diff --git a/go.mod b/go.mod index 00e0bb8..8d5ed75 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/jbub/banking v0.8.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 4990573..3ce9ba2 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jbub/banking v0.8.0 h1:79kXJj1X2E9dWdWuFNkk2Pw7c6uYPFQS8ev0l+zMFxk= +github.com/jbub/banking v0.8.0/go.mod h1:ctv/bD2EGRR5PobFrJSXZ/FZXCFtUbmVv6v2qf/b/88= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= diff --git a/internal/controllers/membershipController.go b/internal/controllers/membershipController.go index 3a333a5..d1a24e5 100644 --- a/internal/controllers/membershipController.go +++ b/internal/controllers/membershipController.go @@ -13,18 +13,15 @@ import ( ) type MembershipController struct { - service services.MembershipService + Service services.MembershipService } + type MembershipData struct { APIKey string `json:"api_key"` Model models.SubscriptionModel `json:"model"` } -func NewMembershipController(service services.MembershipService) *MembershipController { - return &MembershipController{service} -} - -func (uc *MembershipController) RegisterSubscription(c *gin.Context) { +func (mc *MembershipController) RegisterSubscription(c *gin.Context) { var regData MembershipData if err := c.ShouldBindJSON(®Data); err != nil { logger.Error.Printf("Couln't decode subscription data: %v", err) @@ -46,7 +43,7 @@ func (uc *MembershipController) RegisterSubscription(c *gin.Context) { logger.Info.Printf("registering subscription: %+v", regData) // Register Subscription - id, err := uc.service.RegisterSubscription(®Data.Model) + id, err := mc.Service.RegisterSubscription(®Data.Model) if err != nil { logger.Error.Printf("Couldn't register Membershipmodel: %v", err) c.JSON(http.StatusInternalServerError, "Couldn't register Membershipmodel") diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index 1203a59..621c7f4 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -4,28 +4,23 @@ import ( "GoMembership/internal/models" "GoMembership/internal/services" - "github.com/gin-gonic/gin" "net/http" + "github.com/gin-gonic/gin" + "GoMembership/pkg/logger" ) type UserController struct { - service services.UserService - emailService services.EmailService - consentService services.ConsentService - bankAccountService services.BankAccountService - membershipService services.MembershipService + Service services.UserServiceInterface + EmailService *services.EmailService + ConsentService services.ConsentServiceInterface + BankAccountService services.BankAccountServiceInterface + MembershipService services.MembershipServiceInterface } type RegistrationData struct { - User models.User `json:"user"` - BankAccount models.BankAccount `json:"bank_account"` - Membership models.Membership `json:"membership"` -} - -func NewUserController(service services.UserService, emailService *services.EmailService, consentService services.ConsentService, bankAccountService services.BankAccountService, membershipService services.MembershipService) *UserController { - return &UserController{service, *emailService, consentService, bankAccountService, membershipService} + User models.User `json:"user"` } func (uc *UserController) RegisterUser(c *gin.Context) { @@ -37,10 +32,18 @@ func (uc *UserController) RegisterUser(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode userdata"}) return } - logger.Info.Printf("registering user: %v", regData.User) + logger.Info.Printf("registering user: %#v", regData.User) + selectedModel, err := uc.MembershipService.GetModelByName(®Data.User.Membership.SubscriptionModel.Name) + if err != nil { + logger.Error.Printf("No subscription model found: %#v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Not a valid subscription model"}) + return + } + + regData.User.Membership.SubscriptionModel = *selectedModel // Register User - id, token, err := uc.service.RegisterUser(®Data.User) + id, token, err := uc.Service.RegisterUser(®Data.User) if err != nil { logger.Error.Printf("Couldn't register User: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't register User"}) @@ -49,12 +52,12 @@ func (uc *UserController) RegisterUser(c *gin.Context) { regData.User.ID = id // Register Bank Account - _, err = uc.bankAccountService.RegisterBankAccount(®Data.BankAccount) + /* _, err = uc.BankAccountService.RegisterBankAccount(®Data.User.BankAccount) if err != nil { logger.Error.Printf("Couldn't register bank account: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't register User-BankAccount"}) return - } + } */ // Register Consents var consents = [2]models.Consent{ @@ -72,7 +75,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) { }, } for _, consent := range consents { - _, err = uc.consentService.RegisterConsent(&consent) + _, err = uc.ConsentService.RegisterConsent(&consent) if err != nil { logger.Error.Printf("Couldn't register consent: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't register User-consent"}) @@ -81,21 +84,21 @@ func (uc *UserController) RegisterUser(c *gin.Context) { } // Register Membership - _, err = uc.membershipService.RegisterMembership(®Data.Membership) + /* _, err = uc.MembershipService.RegisterMembership(®Data.User.Membership) if err != nil { logger.Error.Printf("Couldn't register membership: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't register User-membership"}) return - } + } */ // Send notifications - if err := uc.emailService.SendVerificationEmail(®Data.User, &token); err != nil { + if err := uc.EmailService.SendVerificationEmail(®Data.User, &token); err != nil { logger.Error.Printf("Failed to send email verification email to user: %v", err) // Proceed without returning error since user registration is successful } // Notify admin of new user registration - if err := uc.emailService.NotifyAdminOfNewUser(®Data.User); err != nil { + if err := uc.EmailService.NotifyAdminOfNewUser(®Data.User); err != nil { logger.Error.Printf("Failed to notify admin of new user registration: %v", err) // Proceed without returning error since user registration is successful } @@ -114,21 +117,21 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) { return } - user, err := uc.service.VerifyUser(&token) + user, err := uc.Service.VerifyUser(&token) if err != nil { logger.Error.Printf("Cannot verify user: %v", err) c.JSON(http.StatusUnauthorized, gin.H{"error": "Cannot verify user"}) return } - membership, err := uc.membershipService.FindMembershipByUserID(user.ID) + membership, err := uc.MembershipService.FindMembershipByUserID(user.ID) if err != nil { logger.Error.Printf("Cannot get membership of user %v: %v", user.ID, err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Cannot get Membership of user"}) return } - uc.emailService.SendWelcomeEmail(user, membership) + uc.EmailService.SendWelcomeEmail(user, membership) c.Status(http.StatusOK) } @@ -138,7 +141,7 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) { Email string `json:"email"` Password string `json:"password"` } - user, err := uc.service.AuthenticateUser(credentials.Email, credentials.Password) + user, err := services.UserService.AuthenticateUser(credentials.Email, credentials.Password) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return @@ -153,7 +156,7 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) { http.Error(w, "Invalid user ID", http.StatusBadRequest) return } - user, err := uc.service.GetUserByID(id) + user, err := services.UserService.GetUserByID(id) if err != nil { http.Error(w, "User not found: "+err.Error(), http.StatusNotFound) } diff --git a/internal/database/db.go b/internal/database/db.go index 2180f2a..6daad24 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -7,13 +7,13 @@ import ( "gorm.io/gorm" ) -// var DB *gorm.DB +var DB *gorm.DB -func InitDB(dbPath string) (*gorm.DB, error) { +func InitDB(dbPath string) error { db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { - return nil, err + return err } if err := db.AutoMigrate( &models.User{}, @@ -23,8 +23,8 @@ func InitDB(dbPath string) (*gorm.DB, error) { &models.Verification{}, &models.BankAccount{}); err != nil { logger.Error.Fatalf("Couldn't create database: %v", err) - return nil, err + return err } - // DB = db - return db, nil + DB = db + return nil } diff --git a/internal/models/bank_account.go b/internal/models/bank_account.go index 0e29606..a1f4b37 100644 --- a/internal/models/bank_account.go +++ b/internal/models/bank_account.go @@ -5,12 +5,12 @@ import "time" type BankAccount struct { CreatedAt time.Time UpdatedAt time.Time - MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"` - Bank string `json:"bank_name"` - AccountHolderName string `json:"account_holder_name"` - IBAN string `gorm:"not null" json:"iban"` - BIC string `json:"bic"` - MandateReference string `gorm:"not null" json:"mandate_reference"` + MandateDateSigned time.Time `gorm:"not null"` // json:"mandate_date_signed"` + Bank string //`json:"bank_name" validate:"omitempty,alphanumunicode"` + AccountHolderName string //`json:"account_holder_name" validate:"omitempty,alphaunicode"` + IBAN string `gorm:"not null" json:"iban" validate:"required,iban"` + BIC string //`json:"bic" validate:"omitempty,bic"` + MandateReference string `gorm:"not null"` //json:"mandate_reference"` ID int64 `gorm:"primaryKey"` - UserID int64 `json:"user_id"` + UserID int64 //`json:"user_id"` } diff --git a/internal/models/membership.go b/internal/models/membership.go index b9f13df..e54082f 100644 --- a/internal/models/membership.go +++ b/internal/models/membership.go @@ -9,7 +9,7 @@ type Membership struct { EndDate time.Time `json:"end_date"` Children *[]User `gorm:"foreignKey:ParentMemberID"` Status string `json:"status"` - SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID"` + SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"` SubscriptionModelID int64 `json:"subsription_model_id"` ID int64 `json:"id"` UserID int64 `json:"user_id"` diff --git a/internal/models/subscription_model.go b/internal/models/subscription_model.go index 915cd4f..b2b1aee 100644 --- a/internal/models/subscription_model.go +++ b/internal/models/subscription_model.go @@ -7,12 +7,12 @@ import ( type SubscriptionModel struct { CreatedAt time.Time UpdatedAt time.Time - Name string `json:"name"` - Details string `json:"details"` - Conditions string `json:"conditions"` + Name string `json:"name" validate:"required,alphaunicode,subscriptionModel"` + Details string `json:"details" validate:"required"` + Conditions string `json:"conditions" validate:"required"` ID int64 `gorm:"primaryKey"` - MonthlyFee float32 `json:"monthly_fee"` - HourlyRate float32 `json:"hourly_rate"` - IncludedPerYear int16 `json:"included_hours_per_year"` - IncludedPerMonth int16 `json:"included_hours_per_month"` + MonthlyFee float32 `json:"monthly_fee" validate:"required,number"` + HourlyRate float32 `json:"hourly_rate" validate:"required,number"` + IncludedPerYear int16 `json:"included_hours_per_year" validate:"omitempty,number"` + IncludedPerMonth int16 `json:"included_hours_per_month" validate:"omitempty,number"` } diff --git a/internal/models/user.go b/internal/models/user.go index 385a1ab..a7a2d16 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -8,24 +8,24 @@ import ( type User struct { UpdatedAt time.Time - DateOfBirth time.Time `gorm:"not null" json:"date_of_birth"` + DateOfBirth time.Time `gorm:"not null" json:"date_of_birth" validate:"required,age"` CreatedAt time.Time Salt *string `json:"-"` - Phone *string `json:"phone"` + Phone string `json:"phone" validate:"omitempty,omitnil,e164"` Notes *string `json:"notes"` - FirstName string `gorm:"not null" json:"first_name"` + FirstName string `gorm:"not null" json:"first_name" validate:"required,alphaunicode"` Password string `json:"password"` - Email string `gorm:"unique;not null" json:"email"` - LastName string `gorm:"not null" json:"last_name"` - ProfilePicture string `json:"profile_picture"` - Address string `gorm:"not null" json:"address"` - ZipCode string `gorm:"not null" json:"zip_code"` - City string `form:"not null" json:"city"` + Email string `gorm:"unique;not null" json:"email" validate:"required,email"` + LastName string `gorm:"not null" json:"last_name" validate:"required,alphaunicode"` + ProfilePicture string `json:"profile_picture" validate:"omitempty,image"` + Address string `gorm:"not null" json:"address" validate:"required"` + ZipCode string `gorm:"not null" json:"zip_code" validate:"required,alphanum"` + City string `form:"not null" json:"city" validate:"required,alphaunicode"` Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"` - BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"` Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` - Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` - ParentMemberID int64 `json:"parent_member_id"` + Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"` + ParentMemberID int64 `json:"parent_member_id" validate:"omitempty,number"` ID int64 `gorm:"primaryKey"` PaymentStatus int8 `json:"payment_status"` Status int8 `json:"status"` diff --git a/internal/repositories/banking_repository.go b/internal/repositories/banking_repository.go index 669412d..464da0e 100644 --- a/internal/repositories/banking_repository.go +++ b/internal/repositories/banking_repository.go @@ -1,24 +1,18 @@ package repositories import ( + "GoMembership/internal/database" "GoMembership/internal/models" - "gorm.io/gorm" ) -type BankAccountRepository interface { +type BankAccountRepositoryInterface interface { CreateBankAccount(account *models.BankAccount) (int64, error) } -type bankAccountRepository struct { - db *gorm.DB -} +type BankAccountRepository struct{} -func NewBankAccountRepository(db *gorm.DB) BankAccountRepository { - return &bankAccountRepository{db} -} - -func (repo *bankAccountRepository) CreateBankAccount(account *models.BankAccount) (int64, error) { - result := repo.db.Create(account) +func (repo *BankAccountRepository) CreateBankAccount(account *models.BankAccount) (int64, error) { + result := database.DB.Create(account) if result.Error != nil { return 0, result.Error } diff --git a/internal/repositories/consents_repository.go b/internal/repositories/consents_repository.go index 0afa468..2373b92 100644 --- a/internal/repositories/consents_repository.go +++ b/internal/repositories/consents_repository.go @@ -1,24 +1,18 @@ package repositories import ( + "GoMembership/internal/database" "GoMembership/internal/models" - "gorm.io/gorm" ) -type ConsentRepository interface { +type ConsentRepositoryInterface interface { CreateConsent(consent *models.Consent) (int64, error) } -type consentRepository struct { - db *gorm.DB -} +type ConsentRepository struct{} -func NewConsentRepository(db *gorm.DB) ConsentRepository { - return &consentRepository{db} -} - -func (repo *consentRepository) CreateConsent(consent *models.Consent) (int64, error) { - result := repo.db.Create(consent) +func (repo *ConsentRepository) CreateConsent(consent *models.Consent) (int64, error) { + result := database.DB.Create(consent) if result.Error != nil { return 0, result.Error diff --git a/internal/repositories/membership_repository.go b/internal/repositories/membership_repository.go index 2323539..3dd29d1 100644 --- a/internal/repositories/membership_repository.go +++ b/internal/repositories/membership_repository.go @@ -1,36 +1,31 @@ package repositories import ( + "GoMembership/internal/database" "gorm.io/gorm" "GoMembership/internal/models" ) -type MembershipRepository interface { +type MembershipRepositoryInterface interface { CreateMembership(membership *models.Membership) (int64, error) FindMembershipByUserID(userID int64) (*models.Membership, error) } -type membershipRepository struct { - db *gorm.DB -} +type MembershipRepository struct{} -func NewMembershipRepository(db *gorm.DB) MembershipRepository { - return &membershipRepository{db} -} - -func (repo *membershipRepository) CreateMembership(membership *models.Membership) (int64, error) { - result := repo.db.Create(membership) +func (repo *MembershipRepository) CreateMembership(membership *models.Membership) (int64, error) { + result := database.DB.Create(membership) if result.Error != nil { return 0, result.Error } return membership.ID, nil } -func (repo *membershipRepository) FindMembershipByUserID(userID int64) (*models.Membership, error) { +func (repo *MembershipRepository) FindMembershipByUserID(userID int64) (*models.Membership, error) { var membership models.Membership - result := repo.db.First(&membership, userID) + result := database.DB.First(&membership, userID) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, gorm.ErrRecordNotFound diff --git a/internal/repositories/subscription_model_repository.go b/internal/repositories/subscription_model_repository.go index 67df4c7..3a75492 100644 --- a/internal/repositories/subscription_model_repository.go +++ b/internal/repositories/subscription_model_repository.go @@ -1,27 +1,40 @@ package repositories import ( - "gorm.io/gorm" + "GoMembership/internal/database" "GoMembership/internal/models" ) -type SubscriptionModelsRepository interface { +type SubscriptionModelsRepositoryInterface interface { CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (int64, error) + GetMembershipModelNames() ([]string, error) + GetModelByName(modelname *string) (*models.SubscriptionModel, error) } -type subscriptionModelsRepository struct { - db *gorm.DB -} +type SubscriptionModelsRepository struct{} -func NewSubscriptionModelsRepository(db *gorm.DB) SubscriptionModelsRepository { - return &subscriptionModelsRepository{db} -} +func (sr *SubscriptionModelsRepository) CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (int64, error) { -func (repo *subscriptionModelsRepository) CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (int64, error) { - result := repo.db.Create(subscriptionModel) + result := database.DB.Create(subscriptionModel) if result.Error != nil { return 0, result.Error } return subscriptionModel.ID, nil } + +func (sr *SubscriptionModelsRepository) GetModelByName(modelname *string) (*models.SubscriptionModel, error) { + var model models.SubscriptionModel + if err := database.DB.Where("name = ?", modelname).First(&model).Error; err != nil { + return nil, err + } + return &model, nil +} + +func (sr *SubscriptionModelsRepository) GetMembershipModelNames() ([]string, error) { + var names []string + if err := database.DB.Model(&models.SubscriptionModel{}).Pluck("name", &names).Error; err != nil { + return []string{}, err + } + return names, nil +} diff --git a/internal/repositories/user_repository.go b/internal/repositories/user_repository.go index 40ada7c..6b9ffe1 100644 --- a/internal/repositories/user_repository.go +++ b/internal/repositories/user_repository.go @@ -3,10 +3,11 @@ package repositories import ( "time" - "GoMembership/internal/constants" - "gorm.io/gorm" + "GoMembership/internal/constants" + "GoMembership/internal/database" + "gorm.io/gorm/clause" "GoMembership/internal/models" @@ -14,7 +15,7 @@ import ( "GoMembership/pkg/logger" ) -type UserRepository interface { +type UserRepositoryInterface interface { CreateUser(user *models.User) (int64, error) UpdateUser(userID int64, user *models.User) error FindUserByID(id int64) (*models.User, error) @@ -24,28 +25,22 @@ type UserRepository interface { VerifyUserOfToken(token *string) (*models.User, error) } -type userRepository struct { - db *gorm.DB -} +type UserRepository struct{} -func NewUserRepository(db *gorm.DB) UserRepository { - return &userRepository{db} -} - -func (repo *userRepository) CreateUser(user *models.User) (int64, error) { - result := repo.db.Create(user) +func (ur *UserRepository) CreateUser(user *models.User) (int64, error) { + result := database.DB.Create(user) if result.Error != nil { return 0, result.Error } return user.ID, nil } -func (repo *userRepository) UpdateUser(userID int64, user *models.User) error { +func (ur *UserRepository) UpdateUser(userID int64, user *models.User) error { logger.Info.Printf("Updating User: %#v\n", user) if user == nil { return errors.ErrNoData } - result := repo.db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) + result := database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user) if result.Error != nil { return result.Error } @@ -57,9 +52,9 @@ func (repo *userRepository) UpdateUser(userID int64, user *models.User) error { return nil } -func (repo *userRepository) FindUserByID(id int64) (*models.User, error) { +func (ur *UserRepository) FindUserByID(id int64) (*models.User, error) { var user models.User - result := repo.db.Preload("Consents").Preload("BankAccount").Preload("Verification").Preload("Membership").First(&user, id) + result := database.DB.Preload("Consents").Preload("BankAccount").Preload("Verification").Preload("Membership").First(&user, id) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, gorm.ErrRecordNotFound @@ -69,9 +64,9 @@ func (repo *userRepository) FindUserByID(id int64) (*models.User, error) { return &user, nil } -func (repo *userRepository) FindUserByEmail(email string) (*models.User, error) { +func (ur *UserRepository) FindUserByEmail(email string) (*models.User, error) { var user models.User - result := repo.db.Where("email = ?", email).First(&user) + result := database.DB.Where("email = ?", email).First(&user) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, gorm.ErrRecordNotFound @@ -81,9 +76,9 @@ func (repo *userRepository) FindUserByEmail(email string) (*models.User, error) return &user, nil } -func (repo *userRepository) IsVerified(userID *int64) (bool, error) { +func (ur *UserRepository) IsVerified(userID *int64) (bool, error) { var user models.User - result := repo.db.Select("status").First(&user, userID) + result := database.DB.Select("status").First(&user, userID) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return false, gorm.ErrRecordNotFound @@ -93,9 +88,9 @@ func (repo *userRepository) IsVerified(userID *int64) (bool, error) { return user.Status != constants.UnverifiedStatus, nil } -func (repo *userRepository) VerifyUserOfToken(token *string) (*models.User, error) { +func (ur *UserRepository) VerifyUserOfToken(token *string) (*models.User, error) { var emailVerification models.Verification - result := repo.db.Where("verification_token = ?", token).First(&emailVerification) + result := database.DB.Where("verification_token = ?", token).First(&emailVerification) if result.Error != nil { if result.Error == gorm.ErrRecordNotFound { return nil, gorm.ErrRecordNotFound @@ -104,11 +99,11 @@ func (repo *userRepository) VerifyUserOfToken(token *string) (*models.User, erro } // Check if the user is already verified - verified, err := repo.IsVerified(&emailVerification.UserID) + verified, err := ur.IsVerified(&emailVerification.UserID) if err != nil { return nil, err } - user, err := repo.FindUserByID(emailVerification.UserID) + user, err := ur.FindUserByID(emailVerification.UserID) if err != nil { return nil, err } @@ -121,7 +116,7 @@ func (repo *userRepository) VerifyUserOfToken(token *string) (*models.User, erro user.Status = constants.VerifiedStatus user.Verification = emailVerification - err = repo.UpdateUser(emailVerification.UserID, user) + err = ur.UpdateUser(emailVerification.UserID, user) if err != nil { return nil, err } @@ -129,9 +124,9 @@ func (repo *userRepository) VerifyUserOfToken(token *string) (*models.User, erro return user, nil } -func (repo *userRepository) SetVerificationToken(user *models.User, token *string) (int64, error) { +func (ur *UserRepository) SetVerificationToken(user *models.User, token *string) (int64, error) { // Check if user is already verified - verified, err := repo.IsVerified(&user.ID) + verified, err := ur.IsVerified(&user.ID) if err != nil { return -1, err } @@ -146,7 +141,7 @@ func (repo *userRepository) SetVerificationToken(user *models.User, token *strin } // Use GORM to insert or update the Verification record - result := repo.db.Clauses(clause.OnConflict{ + result := database.DB.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "user_id"}}, DoUpdates: clause.AssignmentColumns([]string{"verification_token", "created_at"}), }).Create(&verification) diff --git a/internal/routes/routes.go b/internal/routes/routes.go index a88d5b1..c3ceada 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -6,10 +6,10 @@ import ( "github.com/gin-gonic/gin" ) -func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipController *controllers.MembershipController) { +func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController) { router.GET("/backend/verify", userController.VerifyMailHandler) router.POST("/backend/api/register", userController.RegisterUser) - router.POST("/backend/api/register/subscription", membershipController.RegisterSubscription) + router.POST("/backend/api/register/subscription", membershipcontroller.RegisterSubscription) // router.HandleFunc("/login", userController.LoginUser).Methods("POST") } diff --git a/internal/server/server.go b/internal/server/server.go index 87d5a2d..d1632b4 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -6,8 +6,9 @@ import ( "GoMembership/internal/config" "GoMembership/internal/controllers" "GoMembership/internal/database" - // "GoMembership/internal/middlewares" "GoMembership/internal/repositories" + + // "GoMembership/internal/middlewares" "GoMembership/internal/routes" "GoMembership/internal/services" "GoMembership/pkg/logger" @@ -18,23 +19,27 @@ import ( func Run() { cfg := config.LoadConfig() logger.Info.Printf("Config: %+v", cfg) - db, err := database.InitDB(cfg.DB.Path) + err := database.InitDB(cfg.DB.Path) if err != nil { logger.Error.Fatalf("Couldn't init database: %v", err) } emailService := services.NewEmailService(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.User, cfg.SMTP.Password, cfg.SMTP.AdminEmail) - consentRepo := repositories.NewConsentRepository(db) - consentService := services.NewConsentService(consentRepo) - bankAccountRepo := repositories.NewBankAccountRepository(db) - bankAccountService := services.NewBankAccountService(bankAccountRepo) - membershipRepo := repositories.NewMembershipRepository(db) - subscriptionRepo := repositories.NewSubscriptionModelsRepository(db) - membershipService := services.NewMembershipService(membershipRepo, subscriptionRepo) - userRepo := repositories.NewUserRepository(db) - userService := services.NewUserService(userRepo) - userController := controllers.NewUserController(userService, emailService, consentService, bankAccountService, membershipService) - membershipController := controllers.NewMembershipController(membershipService) + var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{} + consentService := &services.ConsentService{Repo: consentRepo} + + var bankAccountRepo repositories.BankAccountRepositoryInterface = &repositories.BankAccountRepository{} + bankAccountService := &services.BankAccountService{Repo: bankAccountRepo} + + var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{} + var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{} + membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo} + + var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{} + userService := &services.UserService{Repo: userRepo} + + userController := &controllers.UserController{Service: userService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService} + membershipController := &controllers.MembershipController{Service: *membershipService} router := gin.Default() // gin.SetMode(gin.ReleaseMode) diff --git a/internal/services/bank_account_service.go b/internal/services/bank_account_service.go index 92f880c..462bd05 100644 --- a/internal/services/bank_account_service.go +++ b/internal/services/bank_account_service.go @@ -10,19 +10,15 @@ import ( "time" ) -type BankAccountService interface { +type BankAccountServiceInterface interface { RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) } -type bankAccountService struct { - repo repositories.BankAccountRepository +type BankAccountService struct { + Repo repositories.BankAccountRepositoryInterface } -func NewBankAccountService(repo repositories.BankAccountRepository) BankAccountService { - return &bankAccountService{repo} -} - -func (service *bankAccountService) RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) { +func (service *BankAccountService) RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) { bankAccount.MandateDateSigned = time.Now() ref, err := generateSEPAMandateReference(strconv.FormatInt(bankAccount.UserID, 10)) if err != nil { @@ -30,7 +26,7 @@ func (service *bankAccountService) RegisterBankAccount(bankAccount *models.BankA } bankAccount.MandateReference = ref - return service.repo.CreateBankAccount(bankAccount) + return service.Repo.CreateBankAccount(bankAccount) } func generateUniqueID(length int) (string, error) { diff --git a/internal/services/consent_service.go b/internal/services/consent_service.go index 57c1eda..69a578b 100644 --- a/internal/services/consent_service.go +++ b/internal/services/consent_service.go @@ -1,25 +1,22 @@ package services import ( + "time" + "GoMembership/internal/models" "GoMembership/internal/repositories" - "time" ) -type ConsentService interface { +type ConsentServiceInterface interface { RegisterConsent(consent *models.Consent) (int64, error) } -type consentService struct { - repo repositories.ConsentRepository +type ConsentService struct { + Repo repositories.ConsentRepositoryInterface } -func NewConsentService(repo repositories.ConsentRepository) ConsentService { - return &consentService{repo} -} - -func (service *consentService) RegisterConsent(consent *models.Consent) (int64, error) { +func (service *ConsentService) RegisterConsent(consent *models.Consent) (int64, error) { consent.CreatedAt = time.Now() consent.UpdatedAt = time.Now() - return service.repo.CreateConsent(consent) + return service.Repo.CreateConsent(consent) } diff --git a/internal/services/email_service.go b/internal/services/email_service.go index 749615f..fd48d9c 100644 --- a/internal/services/email_service.go +++ b/internal/services/email_service.go @@ -129,7 +129,7 @@ func (s *EmailService) NotifyAdminOfNewUser(user *models.User) error { City: user.City, DateOfBirth: user.DateOfBirth.Format("20060102"), Email: user.Email, - Phone: *user.Phone, + Phone: user.Phone, IBAN: user.BankAccount.IBAN, } diff --git a/internal/services/membership_service.go b/internal/services/membership_service.go index 2fe56a4..a909724 100644 --- a/internal/services/membership_service.go +++ b/internal/services/membership_service.go @@ -3,35 +3,50 @@ package services import ( "GoMembership/internal/models" "GoMembership/internal/repositories" + "GoMembership/pkg/errors" + "slices" "time" ) -type MembershipService interface { +type MembershipServiceInterface interface { RegisterMembership(membership *models.Membership) (int64, error) FindMembershipByUserID(userID int64) (*models.Membership, error) RegisterSubscription(subscription *models.SubscriptionModel) (int64, error) + GetMembershipModelNames() ([]string, error) + GetModelByName(modelname *string) (*models.SubscriptionModel, error) } -type membershipService struct { - repo repositories.MembershipRepository - subscriptionRepo repositories.SubscriptionModelsRepository +type MembershipService struct { + Repo repositories.MembershipRepositoryInterface + SubscriptionRepo repositories.SubscriptionModelsRepositoryInterface } -func NewMembershipService(repo repositories.MembershipRepository, subscriptionRepo repositories.SubscriptionModelsRepository) MembershipService { - return &membershipService{repo, subscriptionRepo} -} - -func (service *membershipService) RegisterMembership(membership *models.Membership) (int64, error) { +func (service *MembershipService) RegisterMembership(membership *models.Membership) (int64, error) { membership.StartDate = time.Now() - return service.repo.CreateMembership(membership) + return service.Repo.CreateMembership(membership) } -func (service *membershipService) FindMembershipByUserID(userID int64) (*models.Membership, error) { - return service.repo.FindMembershipByUserID(userID) +func (service *MembershipService) FindMembershipByUserID(userID int64) (*models.Membership, error) { + return service.Repo.FindMembershipByUserID(userID) } // Membership_Subscriptions - -func (service *membershipService) RegisterSubscription(subscription *models.SubscriptionModel) (int64, error) { - return service.subscriptionRepo.CreateSubscriptionModel(subscription) +func (service *MembershipService) RegisterSubscription(subscription *models.SubscriptionModel) (int64, error) { + return service.SubscriptionRepo.CreateSubscriptionModel(subscription) +} + +func (service *MembershipService) GetMembershipModelNames() ([]string, error) { + return service.SubscriptionRepo.GetMembershipModelNames() +} + +func (service *MembershipService) GetModelByName(modelname *string) (*models.SubscriptionModel, error) { + sModelNames, err := service.SubscriptionRepo.GetMembershipModelNames() + if err != nil { + return nil, err + } + + if !slices.Contains(sModelNames, *modelname) { + return nil, errors.ErrNotFound + } + return service.SubscriptionRepo.GetModelByName(modelname) } diff --git a/internal/services/user_service.go b/internal/services/user_service.go index 2e61131..8ed25f6 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -6,47 +6,44 @@ import ( "GoMembership/internal/repositories" "GoMembership/internal/utils" "GoMembership/pkg/logger" - + "github.com/go-playground/validator/v10" // "crypto/rand" // "encoding/base64" // "golang.org/x/crypto/bcrypt" "time" ) -type UserService interface { +type UserServiceInterface interface { RegisterUser(user *models.User) (int64, string, error) // AuthenticateUser(email, password string) (*models.User, error)A VerifyUser(token *string) (*models.User, error) } -type userService struct { - repo repositories.UserRepository +type UserService struct { + Repo repositories.UserRepositoryInterface } -func NewUserService(repo repositories.UserRepository) UserService { - return &userService{repo} -} - -func (service *userService) RegisterUser(user *models.User) (int64, string, error) { +func (service *UserService) RegisterUser(user *models.User) (int64, string, error) { /* salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return -1, err } user.Salt = base64.StdEncoding.EncodeToString(salt) - - hashedPassword, err := HashPassword(user.Password, user.Salt) - if err != nil { - return -1, err - } - user.Password = string(hashedPassword) */ - // TODO: Validate Data - user.Status = constants.UnverifiedStatus - user.CreatedAt = time.Now() - user.UpdatedAt = time.Now() - id, err := service.repo.CreateUser(user) + */ + err := validateRegistrationData(user) if err != nil { return -1, "", err } + + user.Status = constants.UnverifiedStatus + user.CreatedAt = time.Now() + user.UpdatedAt = time.Now() + + id, err := service.Repo.CreateUser(user) + if err != nil { + return -1, "", err + } + user.ID = id token, err := utils.GenerateVerificationToken() @@ -55,21 +52,33 @@ func (service *userService) RegisterUser(user *models.User) (int64, string, erro } logger.Info.Printf("TOKEN: %v", token) - _, err = service.repo.SetVerificationToken(user, &token) + + _, err = service.Repo.SetVerificationToken(user, &token) if err != nil { return -1, "", err } + return id, token, nil } -func (service *userService) VerifyUser(token *string) (*models.User, error) { - user, err := service.repo.VerifyUserOfToken(token) +func (service *UserService) VerifyUser(token *string) (*models.User, error) { + user, err := service.Repo.VerifyUserOfToken(token) if err != nil { return nil, err } return user, nil } +func validateRegistrationData(user *models.User) error { + validate := validator.New() + validate.RegisterValidation("age", utils.AgeValidator) + validate.RegisterValidation("bic", utils.BICValidator) + validate.RegisterValidation("iban", utils.IBANValidator) + validate.RegisterValidation("subscriptionModel", utils.SubscriptionModelValidator) + + return validate.Struct(user) +} + /* func HashPassword(password string, salt string) (string, error) { saltedPassword := password + salt hashedPassword, err := bcrypt.GenerateFromPassword([]byte(saltedPassword), bcrypt.DefaultCost) @@ -80,7 +89,7 @@ func (service *userService) VerifyUser(token *string) (*models.User, error) { return base64.StdEncoding.EncodeToString(hashedPassword), nil } */ -/* func (s *userService) AuthenticateUser(email, password string) (*models.User, error) { +/* func (s *UserService) AuthenticateUser(email, password string) (*models.User, error) { user, err := s.repo.FindUserByEmail(email) if err != nil { return nil, errors.ErrUserNotFound diff --git a/internal/utils/validators.go b/internal/utils/validators.go index 42d8b4d..99e63f8 100644 --- a/internal/utils/validators.go +++ b/internal/utils/validators.go @@ -1,9 +1,62 @@ package utils -import "regexp" +// import "regexp" -func IsEmailValid(email string) bool { - regex := `^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$` - re := regexp.MustCompile(regex) - return re.MatchString(email) +import ( + // "reflect" + "time" + + "GoMembership/internal/database" + "GoMembership/internal/models" + "GoMembership/pkg/logger" + "github.com/go-playground/validator/v10" + "github.com/jbub/banking/iban" + "github.com/jbub/banking/swift" + "slices" +) + +// +// func IsEmailValid(email string) bool { +// regex := `^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$` +// re := regexp.MustCompile(regex) +// return re.MatchString(email) +// } + +func AgeValidator(fl validator.FieldLevel) bool { + fieldValue := fl.Field() + // Ensure the field is of type time.Time + // if fieldValue.Kind() != reflect.Struct || !fieldValue.Type().ConvertibleTo(reflect.TypeOf(time.Time{})) { + // return false + // } + dateOfBirth := fieldValue.Interface().(time.Time) + now := time.Now() + age := now.Year() - dateOfBirth.Year() + + if now.YearDay() < dateOfBirth.YearDay() { + age-- // if birthday is in the future.. + } + + return age >= 18 +} + +func SubscriptionModelValidator(fl validator.FieldLevel) bool { + fieldValue := fl.Field().String() + var names []string + if err := database.DB.Model(&models.SubscriptionModel{}).Pluck("name", &names).Error; err != nil { + logger.Error.Fatalf("Couldn't get SubscriptionModel names: %#v", err) + return false + } + return slices.Contains(names, fieldValue) +} + +func IBANValidator(fl validator.FieldLevel) bool { + fieldValue := fl.Field().String() + + return iban.Validate(fieldValue) == nil +} + +func BICValidator(fl validator.FieldLevel) bool { + fieldValue := fl.Field().String() + + return swift.Validate(fieldValue) == nil }