From 555d1be57589b0b31462ad0573cbe75ab911e3a6 Mon Sep 17 00:00:00 2001 From: "$(pass /github/name)" <$(pass /github/email)> Date: Sun, 7 Jul 2024 21:39:58 +0200 Subject: [PATCH] splitted user registration into user, consents & bankaccount creation --- internal/controllers/user_controller.go | 67 ++++++++++++++++---- internal/database/schema.sql | 6 +- internal/models/bank_account.go | 13 ++++ internal/models/consents.go | 13 ++++ internal/models/user.go | 20 +++--- internal/repositories/banking_repository.go | 35 ++++++++++ internal/repositories/consents_repository.go | 35 ++++++++++ internal/repositories/user_repository.go | 28 ++++---- internal/server/server.go | 6 +- internal/services/bank_account_service.go | 53 ++++++++++++++++ internal/services/consent_service.go | 25 ++++++++ internal/services/email_service.go | 6 +- internal/services/user_service.go | 16 ++--- 13 files changed, 270 insertions(+), 53 deletions(-) create mode 100644 internal/models/bank_account.go create mode 100644 internal/models/consents.go create mode 100644 internal/repositories/banking_repository.go create mode 100644 internal/repositories/consents_repository.go create mode 100644 internal/services/bank_account_service.go create mode 100644 internal/services/consent_service.go diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index fc04322..cec7ada 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -1,9 +1,11 @@ package controllers import ( + "encoding/json" + "GoMembership/internal/models" "GoMembership/internal/services" - "encoding/json" + // "github.com/gorilla/mux" "net/http" // "strconv" @@ -12,46 +14,87 @@ import ( ) type UserController struct { - service services.UserService - emailService services.EmailService + service services.UserService + emailService services.EmailService + consentService services.ConsentService + bankAccountService services.BankAccountService } -func NewUserController(service services.UserService, emailService *services.EmailService) *UserController { - return &UserController{service, *emailService} +type RegistrationData struct { + User models.User `json:"user"` + BankAccount models.BankAccount `json:"bank_account"` +} + +func NewUserController(service services.UserService, emailService *services.EmailService, consentService services.ConsentService, bankAccountService services.BankAccountService) *UserController { + return &UserController{service, *emailService, consentService, bankAccountService} } func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { - logger.Info.Println("registering user") rh := utils.NewResponseHandler(w) - var user models.User - if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + var regData RegistrationData + + if err := json.NewDecoder(r.Body).Decode(®Data); err != nil { // http.Error(w, err.Error(), http.StatusBadRequest) logger.Error.Printf("Couldn't decode Userdata: %v", err) rh.RespondWithError(http.StatusBadRequest, "Couldn't decode userdata") return } - id, err := uc.service.RegisterUser(&user) + logger.Info.Printf("registering user: %v", regData.User) + + id, err := uc.service.RegisterUser(®Data.User) if err != nil { // http.Error(w, err.Error(), http.StatusInternalServerError) logger.Error.Printf("Couldn't register User: %v", err) rh.RespondWithError(http.StatusInternalServerError, "Couldn't register User") return } + regData.User.ID = id + _, err = uc.bankAccountService.RegisterBankAccount(®Data.BankAccount) + if err != nil { + // http.Error(w, err.Error(), http.StatusInternalServerError) + logger.Error.Printf("Couldn't register bank account: %v", err) + rh.RespondWithError(http.StatusInternalServerError, "Couldn't register User") + return + } + var consents = [2]models.Consent{ + { + FirstName: regData.User.FirstName, + LastName: regData.User.LastName, + Email: regData.User.Email, + ConsentType: "TermsOfService", + }, + { + FirstName: regData.User.FirstName, + LastName: regData.User.LastName, + Email: regData.User.Email, + ConsentType: "Privacy", + }, + } + for _, consent := range consents { + _, err = uc.consentService.RegisterConsent(&consent) + if err != nil { + // http.Error(w, err.Error(), http.StatusInternalServerError) + logger.Error.Printf("Couldn't register consent: %v", err) + rh.RespondWithError(http.StatusInternalServerError, "Couldn't register User") + return + } + } + // Send welcome email to the user - if err := uc.emailService.SendWelcomeEmail(user); err != nil { + if err := uc.emailService.SendWelcomeEmail(®Data.User); err != nil { logger.Error.Printf("Failed to send welcome email to user: %v", err) // rh.RespondWithError(http.StatusServiceUnavailable, "User creation succeeded, but failed to send welcome email to user") } // Notify admin of new user registration - if err := uc.emailService.NotifyAdminOfNewUser(user.Email); err != nil { + if err := uc.emailService.NotifyAdminOfNewUser(®Data.User); err != nil { logger.Error.Printf("Failed to notify admin of new user registration: %v", err) // rh.RespondWithError(http.StatusServiceUnavailable, "User creation succeeded, but failed to notify admin of new user registration") } rh.RespondWithJSON(http.StatusCreated, map[string]interface{}{ "status": "success", - "id": id, + "id": regData.User.ID, }) } diff --git a/internal/database/schema.sql b/internal/database/schema.sql index 4a50539..f2af899 100644 --- a/internal/database/schema.sql +++ b/internal/database/schema.sql @@ -71,12 +71,12 @@ CREATE TABLE roles ( CREATE TABLE consents ( id INT PRIMARY KEY, user_id INT, - modified DATE, - created DATE, + updated_at DATE, + created_at DATE, first_name VARCHAR(255), last_name VARCHAR(255), email VARCHAR(255), - type VARCHAR(255), + consent_type VARCHAR(255), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ); diff --git a/internal/models/bank_account.go b/internal/models/bank_account.go new file mode 100644 index 0000000..012ae4f --- /dev/null +++ b/internal/models/bank_account.go @@ -0,0 +1,13 @@ +package models + +import "time" + +type BankAccount struct { + MandateDateSigned time.Time `json:"mandate_date_signed"` + Bank string `json:"bank_name"` + AccountHolderName string `json:"account_holder_name"` + IBAN string `json:"iban"` + BIC string `json:"bic"` + MandateReference string `json:"mandate_reference"` + UserID int `json:"id"` +} diff --git a/internal/models/consents.go b/internal/models/consents.go new file mode 100644 index 0000000..584968d --- /dev/null +++ b/internal/models/consents.go @@ -0,0 +1,13 @@ +package models + +import "time" + +type Consent struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + UpdatedAt time.Time `json:"updated_at"` + CreatedAt time.Time `json:"created_at"` + ConsentType string `json:"consent_type"` + UserID int `json:"id"` +} diff --git a/internal/models/user.go b/internal/models/user.go index eaff4a4..3eed8c4 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -3,16 +3,12 @@ package models import "time" type User struct { - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - MandateDateSigned time.Time `json:"mandate_date_signed"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Email string `json:"email"` - Password string `json:"-"` - Salt string `json:"-"` - IBAN string `json:"iban"` - BIC string `json:"bic"` - MandateReference string `json:"mandate_reference"` - ID int `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Password string `json:"password"` + LastName string `json:"last_name"` + Email string `json:"email"` + FirstName string `json:"first_name"` + Salt string `json:"-"` + ID int64 `json:"id"` } diff --git a/internal/repositories/banking_repository.go b/internal/repositories/banking_repository.go new file mode 100644 index 0000000..eaa78b0 --- /dev/null +++ b/internal/repositories/banking_repository.go @@ -0,0 +1,35 @@ +package repositories + +import ( + "GoMembership/internal/models" + // "GoMembership/pkg/errors" + "database/sql" +) + +type BankAccountRepository interface { + CreateBankAccount(account *models.BankAccount) (int64, error) +} + +type bankAccountRepository struct { + db *sql.DB +} + +func NewBankAccountRepository(db *sql.DB) BankAccountRepository { + return &bankAccountRepository{db} +} + +func (repo *bankAccountRepository) CreateBankAccount(account *models.BankAccount) (int64, error) { + + query := "INSERT INTO banking (user_id, iban, bic, mandate_reference, mandate_date_signed, bank_name, account_holder_name) VALUES (?, ?, ?, ?, ?, ?, ?)" + result, err := repo.db.Exec(query, account.UserID, account.IBAN, account.BIC, account.MandateReference, account.MandateDateSigned, account.Bank, account.AccountHolderName) + if err != nil { + + return -1, err + } + lastInsertID, err := result.LastInsertId() + if err != nil { + return -1, err + } + + return lastInsertID, err +} diff --git a/internal/repositories/consents_repository.go b/internal/repositories/consents_repository.go new file mode 100644 index 0000000..d95bc67 --- /dev/null +++ b/internal/repositories/consents_repository.go @@ -0,0 +1,35 @@ +package repositories + +import ( + "GoMembership/internal/models" + // "GoMembership/pkg/errors" + "database/sql" +) + +type ConsentRepository interface { + CreateConsent(consent *models.Consent) (int64, error) +} + +type consentRepository struct { + db *sql.DB +} + +func NewConsentRepository(db *sql.DB) ConsentRepository { + return &consentRepository{db} +} + +func (repo *consentRepository) CreateConsent(consent *models.Consent) (int64, error) { + + query := "INSERT INTO consents (user_id, first_name, last_name, email, created_at, updated_at, consent_type) VALUES (?, ?, ?, ?, ?, ?, ?)" + result, err := repo.db.Exec(query, consent.UserID, consent.FirstName, consent.LastName, consent.Email, consent.CreatedAt, consent.UpdatedAt, consent.ConsentType) + if err != nil { + + return -1, err + } + lastInsertID, err := result.LastInsertId() + if err != nil { + return -1, err + } + + return lastInsertID, err +} diff --git a/internal/repositories/user_repository.go b/internal/repositories/user_repository.go index c444a46..ef11530 100644 --- a/internal/repositories/user_repository.go +++ b/internal/repositories/user_repository.go @@ -12,9 +12,17 @@ type UserRepository interface { FindUserByEmail(email string) (*models.User, error) } +type userRepository struct { + db *sql.DB +} + +func NewUserRepository(db *sql.DB) UserRepository { + return &userRepository{db} +} + func (repo *userRepository) CreateUser(user *models.User) (int64, error) { - query := "INSERT INTO users (first_name, last_name, email, password, salt, iban, bic, mandate_reference, mandate_date_signed, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - result, err := repo.db.Exec(query, user.FirstName, user.LastName, user.Email, user.Password, user.Salt, user.IBAN, user.BIC, user.MandateReference, user.MandateDateSigned, user.CreatedAt, user.UpdatedAt) + query := "INSERT INTO users (first_name, last_name, email, password, salt, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)" + result, err := repo.db.Exec(query, user.FirstName, user.LastName, user.Email, user.Password, user.Salt, user.CreatedAt, user.UpdatedAt) if err != nil { return -1, err } @@ -27,18 +35,10 @@ func (repo *userRepository) CreateUser(user *models.User) (int64, error) { return lastInsertID, err } -type userRepository struct { - db *sql.DB -} - -func NewUserRepository(db *sql.DB) UserRepository { - return &userRepository{db} -} - func (repo *userRepository) FindUserByID(id int) (*models.User, error) { var user models.User - query := "SELECT id, first_name, last_name, email, iban, bic, mandate_reference FROM users WHERE id = ?" - err := repo.db.QueryRow(query, id).Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &user.IBAN, &user.BIC, &user.MandateReference) + query := "SELECT id, first_name, last_name, email FROM users WHERE id = ?" + err := repo.db.QueryRow(query, id).Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email) if err != nil { if err == sql.ErrNoRows { return nil, errors.ErrUserNotFound @@ -50,8 +50,8 @@ func (repo *userRepository) FindUserByID(id int) (*models.User, error) { func (repo *userRepository) FindUserByEmail(email string) (*models.User, error) { var user models.User - query := "SELECT id, first_name, last_name, email, iban, bic, mandate_reference FROM users WHERE email = ?" - err := repo.db.QueryRow(query, email).Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &user.IBAN, &user.BIC, &user.MandateReference) + query := "SELECT id, first_name, last_name, email FROM users WHERE email = ?" + err := repo.db.QueryRow(query, email).Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email) if err != nil { if err == sql.ErrNoRows { return nil, errors.ErrUserNotFound diff --git a/internal/server/server.go b/internal/server/server.go index 9369bab..32190e3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -21,9 +21,13 @@ func Run() { defer db.Close() 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) userRepo := repositories.NewUserRepository(db) userService := services.NewUserService(userRepo) - userController := controllers.NewUserController(userService, emailService) + userController := controllers.NewUserController(userService, emailService, consentService, bankAccountService) router := mux.NewRouter() // router.Handle("/csrf-token", middlewares.GenerateCSRFTokenHandler()).Methods("GET") diff --git a/internal/services/bank_account_service.go b/internal/services/bank_account_service.go new file mode 100644 index 0000000..d28a902 --- /dev/null +++ b/internal/services/bank_account_service.go @@ -0,0 +1,53 @@ +package services + +import ( + "GoMembership/internal/models" + "GoMembership/internal/repositories" + "crypto/rand" + "encoding/hex" + "fmt" + "strconv" + "time" +) + +type BankAccountService interface { + RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) +} + +type bankAccountService struct { + repo repositories.BankAccountRepository +} + +func NewBankAccountService(repo repositories.BankAccountRepository) BankAccountService { + return &bankAccountService{repo} +} + +func (service *bankAccountService) RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) { + bankAccount.MandateDateSigned = time.Now() + ref, err := generateSEPAMandateReference(strconv.Itoa(bankAccount.UserID)) + if err != nil { + return -1, err + } + bankAccount.MandateReference = ref + + return service.repo.CreateBankAccount(bankAccount) +} + +func generateUniqueID(length int) (string, error) { + bytes := make([]byte, length) + _, err := rand.Read(bytes) + if err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +} + +func generateSEPAMandateReference(userID string) (string, error) { + today := time.Now().Format("20060102") // Format YYYYMMDD + uniqueID, err := generateUniqueID(4) // 4 Bytes = 8 Hex-Zeichen + if err != nil { + return "", err + } + mandateReference := fmt.Sprintf("%s-%s-%s", userID, today, uniqueID) + return mandateReference, nil +} diff --git a/internal/services/consent_service.go b/internal/services/consent_service.go new file mode 100644 index 0000000..57c1eda --- /dev/null +++ b/internal/services/consent_service.go @@ -0,0 +1,25 @@ +package services + +import ( + "GoMembership/internal/models" + "GoMembership/internal/repositories" + "time" +) + +type ConsentService interface { + RegisterConsent(consent *models.Consent) (int64, error) +} + +type consentService struct { + repo repositories.ConsentRepository +} + +func NewConsentService(repo repositories.ConsentRepository) ConsentService { + return &consentService{repo} +} + +func (service *consentService) RegisterConsent(consent *models.Consent) (int64, error) { + consent.CreatedAt = time.Now() + consent.UpdatedAt = time.Now() + return service.repo.CreateConsent(consent) +} diff --git a/internal/services/email_service.go b/internal/services/email_service.go index 4a520aa..da07ee8 100644 --- a/internal/services/email_service.go +++ b/internal/services/email_service.go @@ -53,7 +53,7 @@ func ParseTemplate(filename string, data interface{}) (string, error) { return tplBuffer.String(), nil } -func (s *EmailService) SendWelcomeEmail(user models.User) error { +func (s *EmailService) SendWelcomeEmail(user *models.User) error { // Prepare data to be injected into the template data := struct { FirstName string @@ -72,8 +72,8 @@ func (s *EmailService) SendWelcomeEmail(user models.User) error { return s.SendEmail(user.Email, subject, body) } -func (s *EmailService) NotifyAdminOfNewUser(userEmail string) error { +func (s *EmailService) NotifyAdminOfNewUser(user *models.User) error { subject := "New User Registration" - body := "
A new user has registered with the email: " + userEmail + "
" + body := "A new user has registered with the email: " + user.Email + "
" return s.SendEmail(s.adminEmail, subject, body) } diff --git a/internal/services/user_service.go b/internal/services/user_service.go index d19ad89..f2b32d0 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -4,9 +4,9 @@ import ( "GoMembership/internal/models" "GoMembership/internal/repositories" // "GoMembership/pkg/errors" - "crypto/rand" - "encoding/base64" - "golang.org/x/crypto/bcrypt" + // "crypto/rand" + // "encoding/base64" + // "golang.org/x/crypto/bcrypt" "time" ) @@ -24,7 +24,7 @@ func NewUserService(repo repositories.UserRepository) UserService { } func (service *userService) RegisterUser(user *models.User) (int64, error) { - salt := make([]byte, 16) + /* salt := make([]byte, 16) if _, err := rand.Read(salt); err != nil { return -1, err } @@ -34,21 +34,21 @@ func (service *userService) RegisterUser(user *models.User) (int64, error) { if err != nil { return -1, err } - user.Password = string(hashedPassword) + user.Password = string(hashedPassword) */ + user.Password = "" user.CreatedAt = time.Now() user.UpdatedAt = time.Now() - user.MandateDateSigned = time.Now() return service.repo.CreateUser(user) } -func HashPassword(password string, salt string) (string, error) { +/* func HashPassword(password string, salt string) (string, error) { saltedPassword := password + salt hashedPassword, err := bcrypt.GenerateFromPassword([]byte(saltedPassword), bcrypt.DefaultCost) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(hashedPassword), nil -} +} */ /* func (s *userService) AuthenticateUser(email, password string) (*models.User, error) { user, err := s.repo.FindUserByEmail(email)