splitted user registration into user, consents & bankaccount creation
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
13
internal/models/bank_account.go
Normal file
13
internal/models/bank_account.go
Normal 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"`
|
||||
}
|
||||
13
internal/models/consents.go
Normal file
13
internal/models/consents.go
Normal 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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
35
internal/repositories/banking_repository.go
Normal file
35
internal/repositories/banking_repository.go
Normal 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
|
||||
}
|
||||
35
internal/repositories/consents_repository.go
Normal file
35
internal/repositories/consents_repository.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
53
internal/services/bank_account_service.go
Normal file
53
internal/services/bank_account_service.go
Normal 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
|
||||
}
|
||||
25
internal/services/consent_service.go
Normal file
25
internal/services/consent_service.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user