splitted user registration into user, consents & bankaccount creation

This commit is contained in:
$(pass /github/name)
2024-07-07 21:39:58 +02:00
parent a76d73aa82
commit 555d1be575
13 changed files with 270 additions and 53 deletions

View File

@@ -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(&regData); 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(&regData.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(&regData.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(&regData.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(&regData.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,
})
}

View File

@@ -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
);

View File

@@ -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"`
}

View File

@@ -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"`
}

View File

@@ -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"`
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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 := "<p>A new user has registered with the email: " + userEmail + "</p>"
body := "<p>A new user has registered with the email: " + user.Email + "</p>"
return s.SendEmail(s.adminEmail, subject, body)
}

View File

@@ -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)