Compare commits

..

5 Commits

Author SHA1 Message Date
Alex
2af4575ff2 new db management 2025-03-24 17:46:25 +01:00
Alex
560623788a refactor 2025-03-24 17:46:11 +01:00
Alex
5d55f5a8d9 add new db constraints and foreignKey mode 2025-03-24 17:45:55 +01:00
Alex
28dfe7ecde refactoring 2025-03-24 17:45:33 +01:00
Alex
741145b960 added Opponent 2025-03-24 17:44:45 +01:00
18 changed files with 663 additions and 251 deletions

View File

@@ -18,7 +18,7 @@ func main() {
config.LoadConfig() config.LoadConfig()
db, err := database.Open(config.DB.Path, config.Recipients.AdminEmail) db, err := database.Open(config.DB.Path, config.Recipients.AdminEmail, config.Env == "development")
if err != nil { if err != nil {
logger.Error.Fatalf("Couldn't init database: %v", err) logger.Error.Fatalf("Couldn't init database: %v", err)
} }

View File

@@ -77,12 +77,14 @@ var Priviliges = struct {
} }
var Roles = struct { var Roles = struct {
Opponent int8
Supporter int8 Supporter int8
Member int8 Member int8
Viewer int8 Viewer int8
Editor int8 Editor int8
Admin int8 Admin int8
}{ }{
Opponent: -5,
Supporter: 0, Supporter: 0,
Member: 1, Member: 1,
Viewer: 2, Viewer: 2,

View File

@@ -87,7 +87,7 @@ func TestMain(t *testing.T) {
log.Fatalf("Error setting environment variable: %v", err) log.Fatalf("Error setting environment variable: %v", err)
} }
config.LoadConfig() config.LoadConfig()
db, err := database.Open("test.db", config.Recipients.AdminEmail) db, err := database.Open("test.db", config.Recipients.AdminEmail, true)
if err != nil { if err != nil {
log.Fatalf("Failed to create DB: %#v", err) log.Fatalf("Failed to create DB: %#v", err)
} }
@@ -120,20 +120,28 @@ func TestMain(t *testing.T) {
log.Fatalf("Failed to init Categories: %v", err) log.Fatalf("Failed to init Categories: %v", err)
} }
admin := models.User{ admin := models.User{
FirstName: "Ad", FirstName: "Ad",
LastName: "min", LastName: "min",
Email: "admin@example.com", Email: "admin@example.com",
DateOfBirth: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC), DateOfBirth: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
Company: "SampleCorp", Company: "SampleCorp",
Phone: "+123456789", Phone: "+123456789",
Address: "123 Main Street", Address: "123 Main Street",
ZipCode: "12345", ZipCode: "12345",
City: "SampleCity", City: "SampleCity",
Status: constants.ActiveStatus, Status: constants.ActiveStatus,
RoleID: 8, Password: "",
} Notes: "",
RoleID: constants.Roles.Admin,
Consents: nil,
Verifications: nil,
Membership: nil,
BankAccount: nil,
Licence: &models.Licence{
Status: constants.UnverifiedStatus,
}}
admin.SetPassword("securepassword") admin.SetPassword("securepassword")
database.DB.Create(&admin) admin.Create(db)
validation.SetupValidators(db) validation.SetupValidators(db)
t.Run("userController", func(t *testing.T) { t.Run("userController", func(t *testing.T) {
testUserController(t) testUserController(t)
@@ -267,41 +275,39 @@ func GetMockedFormContext(formData url.Values, url string) (*gin.Context, *httpt
func getBaseUser() models.User { func getBaseUser() models.User {
return models.User{ return models.User{
DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
FirstName: "John", FirstName: "John",
LastName: "Doe", LastName: "Doe",
Email: "john.doe@example.com", Email: "john.doe@example.com",
Address: "Pablo Escobar Str. 4", Address: "Pablo Escobar Str. 4",
ZipCode: "25474", ZipCode: "25474",
City: "Hasloh", City: "Hasloh",
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"}},
Licence: nil, Licence: nil,
ProfilePicture: "", Password: "passw@#$#%$!-ord123",
Password: "passw@#$#%$!-ord123", Company: "",
Company: "", RoleID: 1,
RoleID: 1,
} }
} }
func getBaseSupporter() models.User { func getBaseSupporter() models.User {
return models.User{ return models.User{
DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC), DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
FirstName: "John", FirstName: "John",
LastName: "Rich", LastName: "Rich",
Email: "john.supporter@example.com", Email: "john.supporter@example.com",
Address: "Pablo Escobar Str. 4", Address: "Pablo Escobar Str. 4",
ZipCode: "25474", ZipCode: "25474",
City: "Hasloh", City: "Hasloh",
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"}},
Licence: nil, Licence: nil,
ProfilePicture: "", Password: "passw@#$#%$!-ord123",
Password: "passw@#$#%$!-ord123", Company: "",
Company: "", RoleID: 0,
RoleID: 0,
} }
} }
func deleteTestDB(dbPath string) error { func deleteTestDB(dbPath string) error {

View File

@@ -31,8 +31,10 @@ func setupTestContext() (*TestContext, error) {
testEmail := "john.doe@example.com" testEmail := "john.doe@example.com"
user, err := Uc.Service.FromEmail(&testEmail) user, err := Uc.Service.FromEmail(&testEmail)
if err != nil { if err != nil {
logger.Error.Printf("error fetching user: %#v", err)
return nil, err return nil, err
} }
logger.Error.Printf("found user: %#v", user)
return &TestContext{ return &TestContext{
router: gin.Default(), router: gin.Default(),
response: httptest.NewRecorder(), response: httptest.NewRecorder(),
@@ -60,7 +62,6 @@ func testCreatePasswordHandler(t *testing.T) {
req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body)) req, _ := http.NewRequest("POST", "/password", bytes.NewBuffer(body))
req.AddCookie(AdminCookie) req.AddCookie(AdminCookie)
tc.router.ServeHTTP(tc.response, req) tc.router.ServeHTTP(tc.response, req)
logger.Error.Printf("Test results for %#v", t.Name())
assert.Equal(t, http.StatusAccepted, tc.response.Code) assert.Equal(t, http.StatusAccepted, tc.response.Code)
assert.JSONEq(t, `{"message":"password_change_requested"}`, tc.response.Body.String()) assert.JSONEq(t, `{"message":"password_change_requested"}`, tc.response.Body.String())
err = checkEmailDelivery(tc.user, true) err = checkEmailDelivery(tc.user, true)

View File

@@ -137,9 +137,7 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
} }
type deleteData struct { type deleteData struct {
User struct { ID uint `json:"id" binding:"required,numeric"`
ID uint `json:"id" binding:"required,numeric"`
} `json:"user"`
} }
var data deleteData var data deleteData
@@ -148,13 +146,13 @@ func (uc *UserController) DeleteUser(c *gin.Context) {
return return
} }
if !requestUser.HasPrivilege(constants.Priviliges.Delete) && data.User.ID != requestUser.ID { if !requestUser.HasPrivilege(constants.Priviliges.Delete) && data.ID != requestUser.ID {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized) utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
return return
} }
logger.Error.Printf("Deleting user: %v", data.User) logger.Error.Printf("Deleting user: %v", data)
if err := uc.Service.Delete(&data.User.ID); err != nil { if err := uc.Service.Delete(&data.ID); err != nil {
utils.HandleDeleteUserError(c, err) utils.HandleDeleteUserError(c, err)
return return
} }
@@ -291,12 +289,14 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
LastName: regData.User.LastName, LastName: regData.User.LastName,
Email: regData.User.Email, Email: regData.User.Email,
ConsentType: "TermsOfService", ConsentType: "TermsOfService",
UserID: regData.User.ID,
}, },
{ {
FirstName: regData.User.FirstName, FirstName: regData.User.FirstName,
LastName: regData.User.LastName, LastName: regData.User.LastName,
Email: regData.User.Email, Email: regData.User.Email,
ConsentType: "Privacy", ConsentType: "Privacy",
UserID: regData.User.ID,
}, },
} }

View File

@@ -78,7 +78,6 @@ func testUserController(t *testing.T) {
database.DB.Model(&models.User{}).Where("email = ?", "john.doe@example.com").Update("status", constants.ActiveStatus) database.DB.Model(&models.User{}).Where("email = ?", "john.doe@example.com").Update("status", constants.ActiveStatus)
loginEmail := testLoginHandler(t) loginEmail := testLoginHandler(t)
testCurrentUserHandler(t, loginEmail) testCurrentUserHandler(t, loginEmail)
// creating a admin cookie // creating a admin cookie
c, w, _ := GetMockedJSONContext([]byte(`{ c, w, _ := GetMockedJSONContext([]byte(`{
"email": "admin@example.com", "email": "admin@example.com",
@@ -402,7 +401,6 @@ func validateUser(assert bool, wantDBData map[string]interface{}) error {
if err != nil { if err != nil {
return fmt.Errorf("Error in database ops: %#v", err) return fmt.Errorf("Error in database ops: %#v", err)
} }
if assert != (len(*users) != 0) { if assert != (len(*users) != 0) {
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)
} }
@@ -575,7 +573,7 @@ func testUpdateUser(t *testing.T) {
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{} var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
category, err := licenceRepo.FindCategoryByName("B") category, err := licenceRepo.FindCategoryByName("B")
assert.NoError(t, err) assert.NoError(t, err)
u.Licence.Categories = []models.Category{category} u.Licence.Categories = []*models.Category{&category}
}, },
expectedStatus: http.StatusAccepted, expectedStatus: http.StatusAccepted,
}, },
@@ -594,7 +592,7 @@ func testUpdateUser(t *testing.T) {
category, err := licenceRepo.FindCategoryByName("A") category, err := licenceRepo.FindCategoryByName("A")
category2, err := licenceRepo.FindCategoryByName("BE") category2, err := licenceRepo.FindCategoryByName("BE")
assert.NoError(t, err) assert.NoError(t, err)
u.Licence.Categories = []models.Category{category, category2} u.Licence.Categories = []*models.Category{&category, &category2}
}, },
expectedStatus: http.StatusAccepted, expectedStatus: http.StatusAccepted,
}, },
@@ -612,7 +610,7 @@ func testUpdateUser(t *testing.T) {
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{} var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
category, err := licenceRepo.FindCategoryByName("A") category, err := licenceRepo.FindCategoryByName("A")
assert.NoError(t, err) assert.NoError(t, err)
u.Licence.Categories = []models.Category{category} u.Licence.Categories = []*models.Category{&category}
}, },
expectedStatus: http.StatusAccepted, expectedStatus: http.StatusAccepted,
}, },
@@ -627,7 +625,7 @@ func testUpdateUser(t *testing.T) {
u.LastName = "Doe Updated" u.LastName = "Doe Updated"
u.Phone = "01738484994" u.Phone = "01738484994"
u.Licence.Number = "B072RRE2I50" u.Licence.Number = "B072RRE2I50"
u.Licence.Categories = []models.Category{} u.Licence.Categories = []*models.Category{}
}, },
expectedStatus: http.StatusAccepted, expectedStatus: http.StatusAccepted,
}, },
@@ -806,11 +804,9 @@ func testUpdateUser(t *testing.T) {
assert.Equal(t, updatedUser.Company, updatedUserFromDB.Company, "Company mismatch") assert.Equal(t, updatedUser.Company, updatedUserFromDB.Company, "Company mismatch")
assert.Equal(t, updatedUser.Phone, updatedUserFromDB.Phone, "Phone mismatch") assert.Equal(t, updatedUser.Phone, updatedUserFromDB.Phone, "Phone mismatch")
assert.Equal(t, updatedUser.Notes, updatedUserFromDB.Notes, "Notes 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.Address, updatedUserFromDB.Address, "Address mismatch")
assert.Equal(t, updatedUser.ZipCode, updatedUserFromDB.ZipCode, "ZipCode mismatch") assert.Equal(t, updatedUser.ZipCode, updatedUserFromDB.ZipCode, "ZipCode mismatch")
assert.Equal(t, updatedUser.City, updatedUserFromDB.City, "City 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.Status, updatedUserFromDB.Status, "Status mismatch")
assert.Equal(t, updatedUser.RoleID, updatedUserFromDB.RoleID, "RoleID mismatch") assert.Equal(t, updatedUser.RoleID, updatedUserFromDB.RoleID, "RoleID mismatch")
@@ -839,8 +835,17 @@ func testUpdateUser(t *testing.T) {
assert.Equal(t, updatedUser.Licence.IssuingCountry, updatedUserFromDB.Licence.IssuingCountry, "Licence.IssuingCountry mismatch") assert.Equal(t, updatedUser.Licence.IssuingCountry, updatedUserFromDB.Licence.IssuingCountry, "Licence.IssuingCountry mismatch")
} }
// For slices or more complex nested structures, you might want to use deep equality checks if len(updatedUser.Consents) > 0 {
assert.ElementsMatch(t, updatedUser.Consents, updatedUserFromDB.Consents, "Consents mismatch") for i := range updatedUser.Consents {
assert.Equal(t, updatedUser.Consents[i].ConsentType, updatedUserFromDB.Consents[i].ConsentType, "ConsentType mismatch at index %d", i)
assert.Equal(t, updatedUser.Consents[i].Email, updatedUserFromDB.Consents[i].Email, "ConsentEmail mismatch at index %d", i)
assert.Equal(t, updatedUser.Consents[i].FirstName, updatedUserFromDB.Consents[i].FirstName, "ConsentFirstName mismatch at index %d", i)
assert.Equal(t, updatedUser.Consents[i].LastName, updatedUserFromDB.Consents[i].LastName, "ConsentLastName mismatch at index %d", i)
assert.Equal(t, updatedUser.Consents[i].UserID, updatedUserFromDB.Consents[i].UserID, "Consent UserId mismatch at index %d", i)
}
} else {
assert.Emptyf(t, updatedUserFromDB.Licence.Categories, "Categories aren't empty when they should")
}
if len(updatedUser.Licence.Categories) > 0 { if len(updatedUser.Licence.Categories) > 0 {
for i := range updatedUser.Licence.Categories { for i := range updatedUser.Licence.Categories {
assert.Equal(t, updatedUser.Licence.Categories[i].Name, updatedUserFromDB.Licence.Categories[i].Name, "Category Category mismatch at index %d", i) assert.Equal(t, updatedUser.Licence.Categories[i].Name, updatedUserFromDB.Licence.Categories[i].Name, "Category Category mismatch at index %d", i)
@@ -1272,7 +1277,7 @@ func getTestUsers() []RegisterUserTest {
{ {
Name: "Correct Licence number, should pass", Name: "Correct Licence number, should pass",
WantResponse: http.StatusCreated, WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"email": "john.correctLicenceNumber@example.com"}, WantDBData: map[string]interface{}{"email": "john.correctlicencenumber@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.correctLicenceNumber@example.com" user.Email = "john.correctLicenceNumber@example.com"

View File

@@ -6,6 +6,8 @@ import (
"GoMembership/pkg/logger" "GoMembership/pkg/logger"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"errors"
"fmt"
"time" "time"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
@@ -15,27 +17,55 @@ import (
var DB *gorm.DB var DB *gorm.DB
func Open(dbPath string, adminMail string) (*gorm.DB, error) { func Open(dbPath string, adminMail string, debug bool) (*gorm.DB, error) {
// Add foreign key support and WAL journal mode to DSN
dsn := fmt.Sprintf("%s?_foreign_keys=1&_journal_mode=WAL", dbPath)
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
// Enable PrepareStmt for better performance
PrepareStmt: true,
})
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to connect database: %w", err)
} }
// Verify foreign key support is enabled
var foreignKeyEnabled int
if err := db.Raw("PRAGMA foreign_keys").Scan(&foreignKeyEnabled).Error; err != nil {
return nil, fmt.Errorf("foreign key check failed: %w", err)
}
if foreignKeyEnabled != 1 {
return nil, errors.New("SQLite foreign key constraints not enabled")
}
if debug {
db = db.Debug()
}
// Configure connection pool
sqlDB, err := db.DB()
if err != nil {
return nil, fmt.Errorf("failed to get DB instance: %w", err)
}
sqlDB.SetMaxOpenConns(1) // Required for SQLite in production
sqlDB.SetMaxIdleConns(1)
sqlDB.SetConnMaxLifetime(time.Hour)
if err := db.AutoMigrate( if err := db.AutoMigrate(
&models.User{}, &models.User{},
&models.SubscriptionModel{}, &models.SubscriptionModel{},
&models.Membership{}, &models.Membership{},
&models.Consent{}, &models.Consent{},
&models.Verification{}, &models.Verification{},
&models.BankAccount{},
&models.Licence{}, &models.Licence{},
&models.Category{}, &models.Category{},
&models.Insurance{},
&models.Car{}, &models.Car{},
&models.Location{}, &models.Location{},
&models.Damage{}, &models.Damage{},
&models.BankAccount{}); err != nil { &models.Insurance{},
logger.Error.Fatalf("Couldn't create database: %v", err) ); err != nil {
return nil, err return nil, fmt.Errorf("failed to migrate database: %w", err)
} }
logger.Info.Print("Opened DB") logger.Info.Print("Opened DB")
@@ -78,14 +108,11 @@ func Open(dbPath string, adminMail string) (*gorm.DB, error) {
return nil, err return nil, err
} }
admin, err := createAdmin(adminMail, createdModel.ID) admin, err := createAdmin(adminMail)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := db.Session(&gorm.Session{FullSaveAssociations: true}).Create(&admin) admin.Create(db)
if result.Error != nil {
return nil, result.Error
}
} }
return db, nil return db, nil
@@ -125,7 +152,7 @@ func createLicenceCategories() []models.Category {
// 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) (*models.User, error) {
passwordBytes := make([]byte, 12) passwordBytes := make([]byte, 12)
_, err := rand.Read(passwordBytes) _, err := rand.Read(passwordBytes)
if err != nil { if err != nil {
@@ -146,26 +173,24 @@ func createAdmin(userMail string, subscriptionModelID uint) (*models.User, error
logger.Error.Print("==============================================================") logger.Error.Print("==============================================================")
return &models.User{ return &models.User{
FirstName: "ad", FirstName: "Ad",
LastName: "min", LastName: "Min",
DateOfBirth: time.Now().AddDate(-20, 0, 0), DateOfBirth: time.Now().AddDate(-20, 0, 0),
Password: hash, Password: hash,
Address: "Downhill 4", Company: "",
ZipCode: "99999", Address: "",
City: "TechTown", ZipCode: "",
Phone: "0123455678", City: "",
Email: userMail, Phone: "",
Status: constants.ActiveStatus, Notes: "",
RoleID: constants.Roles.Admin, Email: userMail,
Membership: models.Membership{ Status: constants.ActiveStatus,
Status: constants.DisabledStatus, RoleID: constants.Roles.Admin,
StartDate: time.Now(), Consents: nil,
SubscriptionModelID: subscriptionModelID, Verifications: nil,
}, Membership: nil,
BankAccount: models.BankAccount{}, BankAccount: nil,
Licence: &models.Licence{ Licence: nil,
Status: constants.UnverifiedStatus,
},
}, nil }, nil
//"DE49700500000008447644", //fake //"DE49700500000008447644", //fake
} }

View File

@@ -1,15 +1,50 @@
package models package models
import "time" import (
"GoMembership/pkg/logger"
"time"
"gorm.io/gorm"
)
type BankAccount struct { type BankAccount struct {
ID uint `gorm:"primaryKey"` ID uint `gorm:"primaryKey"`
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"` UserID uint `gorm:"index" json:"user_id"`
MandateDateSigned time.Time `json:"mandate_date_signed"`
Bank string `json:"bank_name" binding:"safe_content"` Bank string `json:"bank_name" binding:"safe_content"`
AccountHolderName string `json:"account_holder_name" binding:"safe_content"` AccountHolderName string `json:"account_holder_name" binding:"safe_content"`
IBAN string `json:"iban" binding:"safe_content"` IBAN string `json:"iban" binding:"safe_content"`
BIC string `json:"bic" binding:"safe_content"` BIC string `json:"bic" binding:"safe_content"`
MandateReference string `gorm:"not null" json:"mandate_reference" binding:"safe_content"` MandateReference string `json:"mandate_reference" binding:"safe_content"`
}
func (b *BankAccount) Create(db *gorm.DB) error {
// b.ID = 0
// only the children the belongs to association gets a reference id
if err := db.Create(b).Error; err != nil {
return err
}
logger.Info.Printf("BankAccount created: %#v", b)
return db.First(b, b.ID).Error // Refresh the object with all associations
}
func (b *BankAccount) Update(db *gorm.DB) error {
var existingBankAccount BankAccount
logger.Info.Printf("updating BankAccount: %#v", b)
if err := db.First(&existingBankAccount, b.ID).Error; err != nil {
return err
}
if err := db.Model(&existingBankAccount).Updates(b).Error; err != nil {
return err
}
return db.First(b, b.ID).Error
}
func (b *BankAccount) Delete(db *gorm.DB) error {
return db.Delete(&b).Error
} }

View File

@@ -0,0 +1,48 @@
package models
import (
"GoMembership/pkg/logger"
"gorm.io/gorm"
)
type Category struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"category" binding:"safe_content"`
}
func (c *Category) 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 {
return err
}
logger.Info.Printf("Category created: %#v", c)
// Preload all associations to return the fully populated User
return tx.
First(c, c.ID).Error // Refresh the user object with all associations
})
}
func (c *Category) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingCategory Category
logger.Info.Printf("updating Category: %#v", c)
if err := tx.First(&existingCategory, c.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingCategory).Updates(c).Error; err != nil {
return err
}
return tx.First(c, c.ID).Error
})
}
func (c *Category) Delete(db *gorm.DB) error {
return db.Delete(&c).Error
}

View File

@@ -1,17 +1,55 @@
package models package models
import ( import (
"GoMembership/pkg/logger"
"strings"
"time" "time"
"gorm.io/gorm"
) )
type Consent struct { type Consent struct {
ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
FirstName string `gorm:"not null" json:"first_name" binding:"safe_content"` FirstName string `gorm:"not null" json:"first_name" binding:"safe_content"`
LastName string `gorm:"not null" json:"last_name" binding:"safe_content"` LastName string `gorm:"not null" json:"last_name" binding:"safe_content"`
Email string `json:"email" binding:"email,safe_content"` Email string `json:"email" binding:"email,safe_content"`
ConsentType string `gorm:"not null" json:"consent_type" binding:"safe_content"` ConsentType string `gorm:"not null" json:"consent_type" binding:"safe_content"`
ID uint `gorm:"primaryKey"` UserID uint `gorm:"not null" json:"user_id"`
User User }
UserID uint
func (c *Consent) BeforeSave(tx *gorm.DB) (err error) {
c.Email = strings.ToLower(c.Email)
return nil
}
func (c *Consent) Create(db *gorm.DB) error {
if err := db.Create(c).Error; err != nil {
return err
}
logger.Info.Printf("Consent created: %#v", c)
return db.First(c, c.ID).Error // Refresh the user object with all associations
}
func (c *Consent) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingConsent Consent
logger.Info.Printf("updating Consent: %#v", c)
if err := tx.First(&existingConsent, c.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingConsent).Updates(c).Error; err != nil {
return err
}
return tx.First(c, c.ID).Error
})
}
func (c *Consent) Delete(db *gorm.DB) error {
return db.Delete(&c).Error
} }

View File

@@ -1,7 +1,11 @@
package models package models
import ( import (
"GoMembership/pkg/logger"
"fmt"
"time" "time"
"gorm.io/gorm"
) )
type Licence struct { type Licence struct {
@@ -9,15 +13,54 @@ type Licence struct {
UserID uint `json:"user_id"` UserID uint `json:"user_id"`
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Status int8 `json:"status" binding:"omitempty,number"` Status int8 `json:"status" binding:"omitempty,number"`
Number string `json:"number" binding:"omitempty,safe_content"` Number string `json:"number" binding:"omitempty,safe_content"`
IssuedDate time.Time `json:"issued_date" binding:"omitempty"` IssuedDate time.Time `json:"issued_date" binding:"omitempty"`
ExpirationDate time.Time `json:"expiration_date" binding:"omitempty"` ExpirationDate time.Time `json:"expiration_date" binding:"omitempty"`
IssuingCountry string `json:"country" binding:"safe_content"` IssuingCountry string `json:"country" binding:"safe_content"`
Categories []Category `json:"categories" gorm:"many2many:licence_2_categories"` Categories []*Category `json:"categories" gorm:"many2many:licence_2_categories"`
} }
type Category struct { func (l *Licence) BeforeSafe(tx *gorm.DB) error {
ID uint `json:"id" gorm:"primaryKey"` if err := tx.Model(l).Association("Categories").Replace(l.Categories); err != nil {
Name string `json:"category" binding:"safe_content"` return fmt.Errorf("failed to link categories: %w", err)
}
return nil
}
func (l *Licence) Create(db *gorm.DB) error {
if err := db.Omit("Categories").Create(l).Error; err != nil {
return err
}
if err := db.Model(&l).Association("Categories").Replace(l.Categories); err != nil {
return err
}
logger.Info.Printf("Licence created: %#v", l)
return db.Preload("Categories").First(l, l.ID).Error // Refresh the object with Categories
}
func (l *Licence) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingLicence Licence
logger.Info.Printf("updating Licence: %#v", l)
if err := tx.First(&existingLicence, l.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingLicence).Updates(l).Error; err != nil {
return err
}
return tx.First(l, l.ID).Error
})
}
func (l *Licence) Delete(db *gorm.DB) error {
return db.Delete(&l).Error
} }

View File

@@ -1,8 +1,15 @@
package models package models
import "time" import (
"GoMembership/pkg/logger"
"time"
"gorm.io/gorm"
)
type Membership struct { type Membership struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
StartDate time.Time `json:"start_date"` StartDate time.Time `json:"start_date"`
@@ -11,5 +18,41 @@ type Membership struct {
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" binding:"omitempty,omitnil,number"` ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"`
ID uint `json:"id"` }
func (m *Membership) BeforeSave(tx *gorm.DB) error {
m.SubscriptionModelID = m.SubscriptionModel.ID
return nil
}
func (m *Membership) Create(db *gorm.DB) error {
if err := db.Create(m).Error; err != nil {
return err
}
logger.Info.Printf("Membership created: %#v", m)
return db.Preload("SubscriptionModel").First(m, m.ID).Error // Refresh the user object with SubscriptionModel
}
func (m *Membership) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingMembership Membership
logger.Info.Printf("updating Membership: %#v", m)
if err := tx.First(&existingMembership, m.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingMembership).Updates(m).Error; err != nil {
return err
}
return tx.First(m, m.ID).Error
})
}
func (m *Membership) Delete(db *gorm.DB) error {
return db.Delete(&m).Error
} }

View File

@@ -1,7 +1,10 @@
package models package models
import ( import (
"GoMembership/pkg/logger"
"time" "time"
"gorm.io/gorm"
) )
type SubscriptionModel struct { type SubscriptionModel struct {
@@ -17,3 +20,39 @@ type SubscriptionModel struct {
IncludedPerYear int16 `json:"included_hours_per_year"` IncludedPerYear int16 `json:"included_hours_per_year"`
IncludedPerMonth int16 `json:"included_hours_per_month"` IncludedPerMonth int16 `json:"included_hours_per_month"`
} }
func (s *SubscriptionModel) 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)
// 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 {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingSubscriptionModel SubscriptionModel
logger.Info.Printf("updating SubscriptionModel: %#v", s)
if err := tx.First(&existingSubscriptionModel, s.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingSubscriptionModel).Updates(s).Error; err != nil {
return err
}
return tx.First(s, s.ID).Error
})
}
func (s *SubscriptionModel) Delete(db *gorm.DB) error {
return db.Delete(&s).Error
}

View File

@@ -8,6 +8,7 @@ import (
"GoMembership/pkg/logger" "GoMembership/pkg/logger"
"fmt" "fmt"
"slices" "slices"
"strings"
"time" "time"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
@@ -18,44 +19,43 @@ import (
) )
type User struct { type User struct {
ID uint `gorm:"primarykey" json:"id"` ID uint `gorm:"primaryKey" json:"id"`
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
DeletedAt *time.Time DeletedAt *time.Time
DateOfBirth time.Time `gorm:"not null" json:"dateofbirth" binding:"required_unless=RoleID 0,safe_content"` DateOfBirth time.Time `gorm:"not null" json:"dateofbirth" binding:"required_unless=RoleID 0,safe_content"`
Company string `json:"company" binding:"omitempty,omitnil,safe_content"` Company string `json:"company" binding:"omitempty,omitnil,safe_content"`
Phone string `json:"phone" binding:"omitempty,omitnil,safe_content"` Phone string `json:"phone" binding:"omitempty,omitnil,safe_content"`
Notes string `json:"notes" binding:"safe_content"` Notes string `json:"notes" binding:"safe_content"`
FirstName string `gorm:"not null" json:"first_name" binding:"required,safe_content"` FirstName string `gorm:"not null" json:"first_name" binding:"required,safe_content"`
Password string `json:"password" binding:"safe_content"` Password string `json:"password" binding:"safe_content"`
Email string `gorm:"uniqueIndex:idx_users_email,not null" json:"email" binding:"required,email,safe_content"` Email string `gorm:"uniqueIndex:idx_users_email,not null" json:"email" binding:"required,email,safe_content"`
LastName string `gorm:"not null" json:"last_name" binding:"required,safe_content"` LastName string `gorm:"not null" json:"last_name" binding:"required,safe_content"`
ProfilePicture string `json:"profile_picture" binding:"omitempty,omitnil,image,safe_content"` Address string `gorm:"not null" json:"address" binding:"required,safe_content"`
Address string `gorm:"not null" json:"address" binding:"required,safe_content"` ZipCode string `gorm:"not null" json:"zip_code" binding:"required,alphanum,safe_content"`
ZipCode string `gorm:"not null" json:"zip_code" binding:"required,alphanum,safe_content"` City string `form:"not null" json:"city" binding:"required,alphaunicode,safe_content"`
City string `form:"not null" json:"city" binding:"required,alphaunicode,safe_content"` Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"` BankAccount *BankAccount `gorm:"foreignkey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"bank_account"`
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"bank_account"` Verifications []Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
BankAccountID uint Membership *Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"membership"`
Verifications *[]Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Licence *Licence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"licence"`
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"membership"` Status int8 `json:"status"`
MembershipID uint RoleID int8 `json:"role_id"`
Licence *Licence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"licence"`
LicenceID uint
PaymentStatus int8 `json:"payment_status"`
Status int8 `json:"status"`
RoleID int8 `json:"role_id"`
} }
func (u *User) AfterCreate(tx *gorm.DB) (err error) { func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.BankAccount.ID != 0 && u.BankAccount.MandateReference == "" { if u.BankAccount != nil && u.BankAccount.MandateReference == "" {
mandateReference := u.GenerateMandateReference() u.BankAccount.MandateReference = u.GenerateMandateReference()
u.BankAccount.Update(tx)
return tx.Model(&u.BankAccount).Update("MandateReference", mandateReference).Error
} }
return nil return nil
} }
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
u.Email = strings.ToLower(u.Email)
return nil
}
func (u *User) GenerateMandateReference() string { func (u *User) GenerateMandateReference() string {
return fmt.Sprintf("%s%d%s", time.Now().Format("20060102"), u.ID, u.BankAccount.IBAN) return fmt.Sprintf("%s%d%s", time.Now().Format("20060102"), u.ID, u.BankAccount.IBAN)
} }
@@ -86,29 +86,128 @@ func (u *User) Delete(db *gorm.DB) error {
} }
func (u *User) Create(db *gorm.DB) error { func (u *User) Create(db *gorm.DB) error {
return db.Transaction(func(tx *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.Preload(clause.Associations).Create(u).Error; err != nil {
if err := tx.Create(u).Error; err != nil {
return err return err
} }
// Replace associated Categories (assumes Categories already exist) return nil
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("Membership").
Preload("Membership.SubscriptionModel").
Preload("Licence").
Preload("Licence.Categories").
First(u, u.ID).Error // Refresh the user object with all associations
}) })
} }
// 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 { func (u *User) Update(db *gorm.DB) error {
err := db.Transaction(func(tx *gorm.DB) error { err := db.Transaction(func(tx *gorm.DB) error {
@@ -117,16 +216,11 @@ func (u *User) Update(db *gorm.DB) error {
logger.Info.Printf("updating user: %#v", u) logger.Info.Printf("updating user: %#v", u)
if err := tx. if err := tx.
Preload("Membership").
Preload("Membership.SubscriptionModel").
Preload("Licence").
Preload("Licence.Categories").
Preload("Verifications").
First(&existingUser, u.ID).Error; err != nil { First(&existingUser, u.ID).Error; err != nil {
return err return err
} }
// Update the user's main fields // Update the user's main fields
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password", "Membership", "Licence", "Verifications").Updates(u) result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password", "Verifications", "Licence.Categories").Updates(u)
if result.Error != nil { if result.Error != nil {
logger.Error.Printf("User update error in update user: %#v", result.Error) logger.Error.Printf("User update error in update user: %#v", result.Error)
return result.Error return result.Error
@@ -143,28 +237,28 @@ func (u *User) Update(db *gorm.DB) error {
} }
} }
// Update the Membership if provided // // Update the Membership if provided
if u.Membership.ID != 0 { // if u.Membership.ID != 0 {
if err := tx.Model(&existingUser.Membership).Updates(u.Membership).Error; err != nil { // 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) // logger.Error.Printf("Membership update error in update user: %#v", err)
return err // return err
} // }
} // }
if u.Licence != nil { // if u.Licence != nil {
u.Licence.UserID = existingUser.ID // u.Licence.UserID = existingUser.ID
if err := tx.Save(u.Licence).Error; err != nil { // if err := tx.Save(u.Licence).Error; err != nil {
return err // return err
} // }
if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil { // if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
return err // return err
} // }
if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil { // if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
return err // return err
} // }
} // }
// if u.Licence != nil { // if u.Licence != nil {
// if existingUser.Licence == nil || existingUser.LicenceID == 0 { // if existingUser.Licence == nil || existingUser.LicenceID == 0 {
// u.Licence.UserID = existingUser.ID // Ensure Licence belongs to User // u.Licence.UserID = existingUser.ID // Ensure Licence belongs to User
@@ -187,7 +281,13 @@ func (u *User) Update(db *gorm.DB) error {
// } // }
if u.Verifications != nil { if u.Verifications != nil {
if err := tx.Save(*u.Verifications).Error; err != nil { if err := tx.Save(u.Verifications).Error; err != nil {
return err
}
}
if u.Licence != nil {
if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
return err return err
} }
} }
@@ -199,11 +299,9 @@ func (u *User) Update(db *gorm.DB) error {
} }
return db. return db.
Preload("Membership"). Preload(clause.Associations).
Preload("Membership.SubscriptionModel"). Preload("Membership.SubscriptionModel").
Preload("Licence").
Preload("Licence.Categories"). Preload("Licence.Categories").
Preload("Verifications").
First(&u, u.ID).Error First(&u, u.ID).Error
} }
@@ -211,11 +309,8 @@ func (u *User) FromID(db *gorm.DB, userID *uint) error {
var user User var user User
result := db. result := db.
Preload(clause.Associations). Preload(clause.Associations).
Preload("Membership").
Preload("Membership.SubscriptionModel"). Preload("Membership.SubscriptionModel").
Preload("Licence").
Preload("Licence.Categories"). Preload("Licence.Categories").
Preload("Verifications").
First(&user, userID) First(&user, userID)
if result.Error != nil { if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {
@@ -231,11 +326,8 @@ func (u *User) FromEmail(db *gorm.DB, email *string) error {
var user User var user User
result := db. result := db.
Preload(clause.Associations). Preload(clause.Associations).
Preload("Membership").
Preload("Membership.SubscriptionModel"). Preload("Membership.SubscriptionModel").
Preload("Licence").
Preload("Licence.Categories"). Preload("Licence.Categories").
Preload("Verifications").
Where("email = ?", email).First(&user) Where("email = ?", email).First(&user)
if result.Error != nil { if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {
@@ -284,7 +376,7 @@ func (u *User) IsSupporter() bool {
func (u *User) SetVerification(verificationType string) (*Verification, error) { func (u *User) SetVerification(verificationType string) (*Verification, error) {
if u.Verifications == nil { if u.Verifications == nil {
u.Verifications = new([]Verification) u.Verifications = []Verification{}
} }
token, err := utils.GenerateVerificationToken() token, err := utils.GenerateVerificationToken()
if err != nil { if err != nil {
@@ -295,10 +387,10 @@ func (u *User) SetVerification(verificationType string) (*Verification, error) {
VerificationToken: token, VerificationToken: token,
Type: verificationType, Type: verificationType,
} }
if vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { return vsl.Type == v.Type }); vi > -1 { 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 { } else {
*u.Verifications = append(*u.Verifications, v) u.Verifications = append(u.Verifications, v)
} }
return &v, nil return &v, nil
} }
@@ -307,11 +399,11 @@ func (u *User) GetVerification(verificationType string) (*Verification, error) {
if u.Verifications == nil { if u.Verifications == nil {
return nil, errors.ErrNoData return nil, errors.ErrNoData
} }
vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { return vsl.Type == verificationType }) vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool { return vsl.Type == verificationType })
if vi == -1 { if vi == -1 {
return nil, errors.ErrNotFound return nil, errors.ErrNotFound
} }
return &(*u.Verifications)[vi], nil return &u.Verifications[vi], nil
} }
func (u *User) Verify(token string, verificationType string) bool { func (u *User) Verify(token string, verificationType string) bool {
@@ -320,7 +412,7 @@ func (u *User) Verify(token string, verificationType string) bool {
return false return false
} }
vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { vi := slices.IndexFunc(u.Verifications, func(vsl Verification) bool {
return vsl.Type == verificationType && vsl.VerificationToken == token return vsl.Type == verificationType && vsl.VerificationToken == token
}) })
@@ -329,32 +421,22 @@ func (u *User) Verify(token string, verificationType string) bool {
return false return false
} }
if (*u.Verifications)[vi].VerifiedAt != nil { if u.Verifications[vi].VerifiedAt != nil {
logger.Error.Printf("VerifiedAt is not nil, already verified?: %#v", (*u.Verifications)[vi]) logger.Error.Printf("VerifiedAt is not nil, already verified?: %#v", u.Verifications[vi])
return false return false
} }
t := time.Now() t := time.Now()
(*u.Verifications)[vi].VerifiedAt = &t u.Verifications[vi].VerifiedAt = &t
return true return true
} }
func (u *User) Safe() map[string]interface{} { func (u *User) Safe() map[string]interface{} {
result := map[string]interface{}{ var membership map[string]interface{} = nil
"email": u.Email, var licence map[string]interface{} = nil
"first_name": u.FirstName, var bankAccount map[string]interface{} = nil
"last_name": u.LastName, if u.Membership != nil {
"phone": u.Phone, membership = map[string]interface{}{
"notes": u.Notes,
"address": u.Address,
"zip_code": u.ZipCode,
"city": u.City,
"status": u.Status,
"id": u.ID,
"role_id": u.RoleID,
"company": u.Company,
"dateofbirth": u.DateOfBirth,
"membership": map[string]interface{}{
"id": u.Membership.ID, "id": u.Membership.ID,
"start_date": u.Membership.StartDate, "start_date": u.Membership.StartDate,
"end_date": u.Membership.EndDate, "end_date": u.Membership.EndDate,
@@ -369,23 +451,11 @@ func (u *User) Safe() map[string]interface{} {
"included_per_year": u.Membership.SubscriptionModel.IncludedPerYear, "included_per_year": u.Membership.SubscriptionModel.IncludedPerYear,
"included_per_month": u.Membership.SubscriptionModel.IncludedPerMonth, "included_per_month": u.Membership.SubscriptionModel.IncludedPerMonth,
}, },
}, }
"licence": map[string]interface{}{
"id": 0,
},
"bank_account": map[string]interface{}{
"id": u.BankAccount.ID,
"mandate_date_signed": u.BankAccount.MandateDateSigned,
"bank": u.BankAccount.Bank,
"account_holder_name": u.BankAccount.AccountHolderName,
"iban": u.BankAccount.IBAN,
"bic": u.BankAccount.BIC,
"mandate_reference": u.BankAccount.MandateReference,
},
} }
if u.Licence != nil { if u.Licence != nil {
result["licence"] = map[string]interface{}{ licence = map[string]interface{}{
"id": u.Licence.ID, "id": u.Licence.ID,
"number": u.Licence.Number, "number": u.Licence.Number,
"status": u.Licence.Status, "status": u.Licence.Status,
@@ -396,6 +466,36 @@ func (u *User) Safe() map[string]interface{} {
} }
} }
if u.BankAccount != nil {
bankAccount = map[string]interface{}{
"id": u.BankAccount.ID,
"mandate_date_signed": u.BankAccount.MandateDateSigned,
"bank": u.BankAccount.Bank,
"account_holder_name": u.BankAccount.AccountHolderName,
"iban": u.BankAccount.IBAN,
"bic": u.BankAccount.BIC,
"mandate_reference": u.BankAccount.MandateReference,
}
}
result := map[string]interface{}{
"email": u.Email,
"first_name": u.FirstName,
"last_name": u.LastName,
"phone": u.Phone,
"notes": u.Notes,
"address": u.Address,
"zip_code": u.ZipCode,
"city": u.City,
"status": u.Status,
"id": u.ID,
"role_id": u.RoleID,
"company": u.Company,
"dateofbirth": u.DateOfBirth,
"membership": membership,
"licence": licence,
"bank_account": bankAccount,
}
return result return result
} }
@@ -439,14 +539,12 @@ func extractUserIDFrom(tokenString string) (uint, error) {
} }
func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) { func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) {
logger.Error.Printf("where: %#v", where)
var users []User var users []User
result := db. result := db.
Preload(clause.Associations). Preload(clause.Associations).
Preload("Membership").
Preload("Membership.SubscriptionModel"). Preload("Membership.SubscriptionModel").
Preload("Licence").
Preload("Licence.Categories"). Preload("Licence.Categories").
Preload("Verifications").
Where(where).Find(&users) Where(where).Find(&users)
if result.Error != nil { if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound { if result.Error == gorm.ErrRecordNotFound {

View File

@@ -1,15 +1,48 @@
package models package models
import ( import (
"GoMembership/pkg/logger"
"time" "time"
"gorm.io/gorm"
) )
type Verification struct { type Verification struct {
UpdatedAt time.Time gorm.Model
CreatedAt time.Time
VerifiedAt *time.Time `json:"verified_at"` VerifiedAt *time.Time `json:"verified_at"`
VerificationToken string `json:"token"` VerificationToken string `json:"token"`
ID uint `gorm:"primaryKey"`
UserID uint `json:"user_id"` UserID uint `json:"user_id"`
Type string Type string
} }
func (v *Verification) Create(db *gorm.DB) error {
if err := db.Create(v).Error; err != nil {
return err
}
logger.Info.Printf("verification created: %#v", v)
// Preload all associations to return the fully populated object
return db.First(v, v.ID).Error // Refresh the verification object with all associations
}
func (v *Verification) Update(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database
var existingVerification Verification
logger.Info.Printf("updating verification: %#v", v)
if err := tx.First(&existingVerification, v.ID).Error; err != nil {
return err
}
if err := tx.Model(&existingVerification).Updates(v).Error; err != nil {
return err
}
return tx.First(v, v.ID).Error
})
}
func (v *Verification) Delete(db *gorm.DB) error {
return db.Delete(&v).Error
}

View File

@@ -83,7 +83,7 @@ func GetUsersBySubscription(subscriptionID uint) (*[]models.User, error) {
Preload("BankAccount"). Preload("BankAccount").
Preload("Licence"). Preload("Licence").
Preload("Licence.Categories"). Preload("Licence.Categories").
Joins("JOIN memberships ON users.membership_id = memberships.id"). Joins("JOIN memberships ON users.id = memberships.user_id").
Joins("JOIN subscription_models ON memberships.subscription_model_id = subscription_models.id"). Joins("JOIN subscription_models ON memberships.subscription_model_id = subscription_models.id").
Where("subscription_models.id = ?", subscriptionID). Where("subscription_models.id = ?", subscriptionID).
Find(&users).Error Find(&users).Error

View File

@@ -68,14 +68,11 @@ func (s *UserService) Update(user *models.User) (*models.User, error) {
if err := existingUser.FromID(s.DB, &user.ID); err != nil { if err := existingUser.FromID(s.DB, &user.ID); err != nil {
return nil, err return nil, err
} }
user.MembershipID = existingUser.MembershipID
user.Membership.ID = existingUser.Membership.ID user.Membership.ID = existingUser.Membership.ID
if existingUser.Licence != nil { if existingUser.Licence != nil {
user.Licence.ID = existingUser.Licence.ID user.Licence.ID = existingUser.Licence.ID
user.LicenceID = existingUser.LicenceID
} }
user.BankAccount.ID = existingUser.BankAccount.ID user.BankAccount.ID = existingUser.BankAccount.ID
user.BankAccountID = existingUser.BankAccountID
user.SetPassword(user.Password) user.SetPassword(user.Password)
@@ -109,7 +106,6 @@ func (s *UserService) Register(user *models.User) (id uint, token string, err er
user.Membership.SubscriptionModel = *selectedModel user.Membership.SubscriptionModel = *selectedModel
user.Membership.SubscriptionModelID = selectedModel.ID user.Membership.SubscriptionModelID = selectedModel.ID
user.Status = constants.UnverifiedStatus user.Status = constants.UnverifiedStatus
user.PaymentStatus = constants.AwaitingPaymentStatus
user.BankAccount.MandateDateSigned = time.Now() user.BankAccount.MandateDateSigned = time.Now()
v, err := user.SetVerification(constants.VerificationTypes.Email) v, err := user.SetVerification(constants.VerificationTypes.Email)
if err != nil { if err != nil {

View File

@@ -10,7 +10,7 @@ type User struct {
Age int Age int
Address *Address Address *Address
Tags []string Tags []string
License License Licence Licence
} }
type Address struct { type Address struct {
@@ -18,7 +18,7 @@ type Address struct {
Country string Country string
} }
type License struct { type Licence struct {
ID string ID string
Categories []string Categories []string
} }
@@ -98,22 +98,22 @@ func TestFilterAllowedStructFields(t *testing.T) {
{ {
name: "Filter slice of structs", name: "Filter slice of structs",
input: &User{ input: &User{
License: License{ Licence: Licence{
ID: "123", ID: "123",
Categories: []string{"A", "B"}, Categories: []string{"A", "B"},
}, },
}, },
existing: &User{ existing: &User{
License: License{ Licence: Licence{
ID: "456", ID: "456",
Categories: []string{"C"}, Categories: []string{"C"},
}, },
}, },
allowedFields: map[string]bool{ allowedFields: map[string]bool{
"License.ID": true, "Licence.ID": true,
}, },
expectedResult: &User{ expectedResult: &User{
License: License{ Licence: Licence{
ID: "123", // Allowed field ID: "123", // Allowed field
Categories: []string{"C"}, // Kept from existing Categories: []string{"C"}, // Kept from existing
}, },