diff --git a/configs/config.template.json b/configs/config.template.json index 041f2c4..6643eaa 100644 --- a/configs/config.template.json +++ b/configs/config.template.json @@ -11,6 +11,6 @@ "AdminEmail": "admin@server.com" }, "templates": { - "MailDir": "templates" + "MailDir": "templates/email" } } diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index cec7ada..8992443 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -18,15 +18,17 @@ type UserController struct { emailService services.EmailService consentService services.ConsentService bankAccountService services.BankAccountService + membershipService services.MembershipService } 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) *UserController { - return &UserController{service, *emailService, consentService, bankAccountService} +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} } func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { @@ -42,7 +44,8 @@ func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { } logger.Info.Printf("registering user: %v", regData.User) - id, err := uc.service.RegisterUser(®Data.User) + // Register User + id, token, 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) @@ -50,13 +53,16 @@ func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { return } regData.User.ID = id + + // Register Bank Account _, 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") + rh.RespondWithError(http.StatusInternalServerError, "Couldn't register User-BankAccount") return } + + // Register Consents var consents = [2]models.Consent{ { FirstName: regData.User.FirstName, @@ -74,19 +80,25 @@ func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { 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") + rh.RespondWithError(http.StatusInternalServerError, "Couldn't register User-consent") return } } - // Send welcome email to the user - 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") + // Register Membership + _, err = uc.membershipService.RegisterMembership(®Data.Membership) + if err != nil { + logger.Error.Printf("Couldn't register membership: %v", err) + rh.RespondWithError(http.StatusInternalServerError, "Couldn't register User-membership") + return } + // Send notifications + if err := uc.emailService.SendVerificationEmail(®Data.User, &token); err != nil { + logger.Error.Printf("Failed to send email verification 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(®Data.User); err != nil { logger.Error.Printf("Failed to notify admin of new user registration: %v", err) @@ -98,6 +110,30 @@ func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { }) } +func (uc *UserController) VerifyMailHandler(w http.ResponseWriter, r *http.Request) { + rh := utils.NewResponseHandler(w) + + token := r.URL.Query().Get("token") + if token == "" { + logger.Error.Println("Missing token to verify mail") + rh.RespondWithError(http.StatusNoContent, "Missing token") + return + } + + user, err := uc.service.VerifyUser(&token) + if err != nil { + logger.Error.Printf("Cannot verify user: %v", err) + rh.RespondWithError(http.StatusUnauthorized, "Cannot verify user") + } + + membership, err := uc.membershipService.FindMembershipByUserID(user.ID) + if err != nil { + logger.Error.Printf("Cannot get membership of user %v: %v", user.ID, err) + rh.RespondWithError(http.StatusInternalServerError, "Cannot get Membership of user") + } + uc.emailService.SendWelcomeEmail(user, membership) +} + /* func (uc *UserController) LoginUser(w http.ResponseWriter, r *http.Request) { var credentials struct { Email string `json:"email"` diff --git a/internal/database/schema.sql b/internal/database/schema.sql index f2af899..2d44a83 100644 --- a/internal/database/schema.sql +++ b/internal/database/schema.sql @@ -1,5 +1,5 @@ CREATE TABLE users ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, first_name VARCHAR(50) NOT NULL, @@ -11,7 +11,6 @@ CREATE TABLE users ( drivers_id_checked BOOLEAN, role_id INT, payment_status VARCHAR(50), - membership_status VARCHAR(50), date_of_birth DATE, address VARCHAR(255), profile_picture VARCHAR(255), @@ -21,7 +20,7 @@ CREATE TABLE users ( ); CREATE TABLE banking ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, user_id INT, iban VARCHAR(34) NOT NULL, bic VARCHAR(11) NOT NULL, @@ -32,25 +31,31 @@ CREATE TABLE banking ( FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -CREATE TABLE membership ( - id INT PRIMARY KEY, - user_id INT, - membership_fee DECIMAL(10, 2), - rental_fee DECIMAL(10, 2), +CREATE TABLE memberships ( + id INTEGER PRIMARY KEY, + user_id INT NOT NULL, + parent_id INT, + model_id INT, + start_date DATE, + end_date DATE, + status VARCHAR(50), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE membership_plans ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + monthly_fee DECIMAL(10, 2), + hourly_rate DECIMAL(10, 2), included_hours_per_year INT, remaining_hours_per_year INT, included_hours_per_month INT, remaining_hours_per_month INT, - membership_model VARCHAR(50), - membership_id VARCHAR(50), - membership_start_date DATE, - membership_end_date DATE, - discount_rate DECIMAL(5, 2), - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + details TEXT ); CREATE TABLE rentals ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, car_id INT, user_id INT, start_datetime DATETIME NOT NULL, @@ -64,12 +69,12 @@ CREATE TABLE rentals ( ); CREATE TABLE roles ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, role_name VARCHAR(50) NOT NULL ); CREATE TABLE consents ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, user_id INT, updated_at DATE, created_at DATE, @@ -81,7 +86,7 @@ CREATE TABLE consents ( ); CREATE TABLE cars ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, licence_plate VARCHAR(15) NOT NULL UNIQUE, insurance VARCHAR(255) NOT NULL, acquired_date DATE NOT NULL, @@ -100,10 +105,10 @@ CREATE TABLE cars ( ); CREATE TABLE email_verifications ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, user_id INT, verification_token VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, - verified_at DATETIME, + verified_at DATETIME DEFAULT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); diff --git a/internal/models/membership.go b/internal/models/membership.go new file mode 100644 index 0000000..1a5e60f --- /dev/null +++ b/internal/models/membership.go @@ -0,0 +1,12 @@ +package models + +import "time" + +type Membership struct { + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + Status string `json:"status"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + ParentID int64 `json:"parent_id"` +} diff --git a/internal/models/membership_plans.go b/internal/models/membership_plans.go new file mode 100644 index 0000000..321fd12 --- /dev/null +++ b/internal/models/membership_plans.go @@ -0,0 +1,10 @@ +package models + +type MembershipPlan struct { + Name string `json:"name"` + MonthlyFee float32 `json:"monthly_fee"` + HourlyRate float32 `json:"hourly_rate"` + IncludedPerYear int16 `json:"included_hours_per_year"` + IncludedPerMonth int16 `json:"included_hours_per_month"` + Details string `json:"details"` +} diff --git a/internal/models/user.go b/internal/models/user.go index 3eed8c4..779235a 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -3,12 +3,21 @@ package models import "time" type User struct { - 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"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DateOfBirth time.Time `json:"date_of_birth"` + Email string `json:"email"` + ProfilePicture string `json:"profile_picture"` + FirstName string `json:"first_name"` + Salt string `json:"-"` + LastName string `json:"last_name"` + Phone string `json:"phone"` + Status string `json:"status"` + Notes string `json:"notes"` + PaymentStatus string `json:"payment_status"` + Password string `json:"password"` + Address string `json:"address"` + ID int64 `json:"id"` + RoleID int8 `json:"role_id"` + DriversIDChecked bool `json:"drivers_id_checked"` } diff --git a/internal/repositories/membership_repository.go b/internal/repositories/membership_repository.go new file mode 100644 index 0000000..0f11d91 --- /dev/null +++ b/internal/repositories/membership_repository.go @@ -0,0 +1,53 @@ +package repositories + +import ( + "database/sql" + "time" + + "GoMembership/internal/models" + "GoMembership/pkg/errors" +) + +type MembershipRepository interface { + CreateMembership(account *models.Membership) (int64, error) + FindMembershipByUserID(userID int64) (*models.Membership, error) +} + +type membershipRepository struct { + db *sql.DB +} + +func NewMembershipRepository(db *sql.DB) MembershipRepository { + return &membershipRepository{db} +} + +func (repo *membershipRepository) CreateMembership(membership *models.Membership) (int64, error) { + + query := "INSERT INTO memberships (user_id, model_id, start_date, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + result, err := repo.db.Exec(query, membership.UserID, membership.MonthlyFee, membership.RentalFee, membership.Model, time.Now(), membership.Status) + if err != nil { + + return -1, err + } + lastInsertID, err := result.LastInsertId() + if err != nil { + return -1, err + } + + return lastInsertID, err +} + +func (repo *membershipRepository) FindMembershipByUserID(userID int64) (*models.Membership, error) { + var membership models.Membership + query := "SELECT id, model_id, start_date, end_date, status FROM memberships where user_id = ?" + err := repo.db.QueryRow(query, userID).Scan(&membership.ID, &membership.MonthlyFee, &membership.RentalFee, &membership.Model, &membership.StartDate, &membership.EndDate, &membership.Status) + if err != nil { + if err == sql.ErrNoRows { + return nil, errors.ErrNotFound + } + return nil, err + } + membership.UserID = userID + return &membership, nil + +} diff --git a/internal/repositories/user_repository.go b/internal/repositories/user_repository.go index ef11530..fbe1917 100644 --- a/internal/repositories/user_repository.go +++ b/internal/repositories/user_repository.go @@ -4,12 +4,18 @@ import ( "GoMembership/internal/models" "GoMembership/pkg/errors" "database/sql" + "fmt" + "strings" + "time" ) type UserRepository interface { CreateUser(user *models.User) (int64, error) - FindUserByID(id int) (*models.User, error) + FindUserByID(id int64) (*models.User, error) FindUserByEmail(email string) (*models.User, error) + SetVerificationToken(user *models.User, token *string) (int64, error) + IsVerified(userID *int64) (bool, error) + VerifyUserOfToken(token *string) (int64, error) } type userRepository struct { @@ -21,8 +27,24 @@ func NewUserRepository(db *sql.DB) UserRepository { } func (repo *userRepository) CreateUser(user *models.User) (int64, error) { - 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) + query := "INSERT INTO users (first_name, last_name, email, phone, drivers_id_checked, role_id, payment_status, date_of_birth, address, profile_picture, notes, status, password, salt, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + result, err := repo.db.Exec(query, + user.FirstName, + user.LastName, + user.Email, + user.Phone, + user.DriversIDChecked, + user.RoleID, + user.PaymentStatus, + user.DateOfBirth, + user.Address, + user.ProfilePicture, + user.Notes, + user.Status, + user.Password, + user.Salt, + user.CreatedAt, + user.UpdatedAt) if err != nil { return -1, err } @@ -31,14 +53,50 @@ func (repo *userRepository) CreateUser(user *models.User) (int64, error) { if err != nil { return -1, err } - return lastInsertID, err } -func (repo *userRepository) FindUserByID(id int) (*models.User, error) { +func (repo *userRepository) UpdateUser(userID int64, updates map[string]interface{}) error { + if len(updates) == 0 { + return errors.ErrNoData + } + + // Construct the query + setClauses := make([]string, 0, len(updates)) + args := make([]interface{}, 0, len(updates)+1) + for column, value := range updates { + setClauses = append(setClauses, fmt.Sprintf("%s = ?", column)) + args = append(args, value) + } + args = append(args, userID) + + query := fmt.Sprintf("UPDATE users SET %s WHERE id = ?", strings.Join(setClauses, ", ")) + + // Execute the query + _, err := repo.db.Exec(query, args...) + if err != nil { + return err + } + + return nil +} + +func (repo *userRepository) FindUserByID(id int64) (*models.User, error) { var user models.User - 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) + query := "SELECT id, first_name, last_name, email, phone, drivers_id_checked, role_id, payment_status, date_of_birth, address, profile_picture, notes, status FROM users WHERE id = ?" + err := repo.db.QueryRow(query, id).Scan(&user.ID, + &user.FirstName, + &user.LastName, + &user.Email, + &user.Phone, + &user.DriversIDChecked, + &user.RoleID, + &user.PaymentStatus, + &user.DateOfBirth, + &user.Address, + &user.ProfilePicture, + &user.Notes, + &user.Status) if err != nil { if err == sql.ErrNoRows { return nil, errors.ErrUserNotFound @@ -60,3 +118,70 @@ func (repo *userRepository) FindUserByEmail(email string) (*models.User, error) } return &user, nil } + +func (repo *userRepository) IsVerified(userID *int64) (bool, error) { + var status string + query := "SELECT status FROM users where id = ?" + err := repo.db.QueryRow(query, userID).Scan(&status) + if err != nil { + if err == sql.ErrNoRows { + return false, errors.ErrUserNotFound + } + return false, err + } + return status != "unverified", nil +} + +func (repo *userRepository) VerifyUserOfToken(token *string) (int64, error) { + var userID int64 + err := repo.db.QueryRow("SELECT user_id FROM email_verifications WHERE verification_token = $1", token).Scan(&userID) + if err == sql.ErrNoRows { + return -1, errors.ErrTokenNotFound + } else if err != nil { + return -1, err + } + verified, err := repo.IsVerified(&userID) + if err != nil { + return -1, err + } + if verified { + return userID, errors.ErrAlreadyVerified + } + update := map[string]interface{}{ + "status": "active", + } + + err = repo.UpdateUser(userID, update) + if err != nil { + return -1, err + } + query := "UPDATE email_verifications SET verified_at = ? WHERE user_id = ?" + _, err = repo.db.Exec(query, time.Now(), userID) + if err != nil { + return -1, err + } + return userID, nil +} + +func (repo *userRepository) SetVerificationToken(user *models.User, token *string) (int64, error) { + // check if user already verified + verified, err := repo.IsVerified(&user.ID) + if err != nil { + return -1, err + } + if verified { + return -1, errors.ErrAlreadyVerified + } + + query := "INSERT OR REPLACE INTO email_verifications (user_id, verification_token, created_at) VALUES (?, ?, ?)" + result, err := repo.db.Exec(query, user.ID, token, time.Now()) + if err != nil { + return -1, err + } + lastInsertID, err := result.LastInsertId() + if err != nil { + return -1, err + } + + return lastInsertID, nil +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index bbd764b..f81b7ed 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -11,7 +11,7 @@ import ( func RegisterRoutes(router *mux.Router, userController *controllers.UserController) { logger.Info.Println("Registering /api/register route") - // router.HandleFunc("/", homeHandler) + router.HandleFunc("/backend/api/verify", userController.VerifyMailHandler).Methods("GET") router.HandleFunc("/backend/api/register", userController.RegisterUser).Methods("POST") // router.HandleFunc("/login", userController.LoginUser).Methods("POST") diff --git a/internal/server/server.go b/internal/server/server.go index 32190e3..7748ef7 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -25,9 +25,11 @@ func Run() { consentService := services.NewConsentService(consentRepo) bankAccountRepo := repositories.NewBankAccountRepository(db) bankAccountService := services.NewBankAccountService(bankAccountRepo) + membershipRepo := repositories.NewMembershipRepository(db) + membershipService := services.NewMembershipService(membershipRepo) userRepo := repositories.NewUserRepository(db) userService := services.NewUserService(userRepo) - userController := controllers.NewUserController(userService, emailService, consentService, bankAccountService) + userController := controllers.NewUserController(userService, emailService, consentService, bankAccountService, membershipService) router := mux.NewRouter() // router.Handle("/csrf-token", middlewares.GenerateCSRFTokenHandler()).Methods("GET") diff --git a/internal/services/email_service.go b/internal/services/email_service.go index da07ee8..5598bee 100644 --- a/internal/services/email_service.go +++ b/internal/services/email_service.go @@ -53,14 +53,43 @@ func ParseTemplate(filename string, data interface{}) (string, error) { return tplBuffer.String(), nil } -func (s *EmailService) SendWelcomeEmail(user *models.User) error { + +func (s *EmailService) SendVerificationEmail(user *models.User, token *string) error { // Prepare data to be injected into the template data := struct { FirstName string LastName string + Token string }{ FirstName: user.FirstName, LastName: user.LastName, + Token: *token, + } + + subject := "Nur noch ein kleiner Schritt!" + body, err := ParseTemplate("mail_verification.html", data) + if err != nil { + logger.Error.Print("Couldn't send verification mail") + return err + } + return s.SendEmail(user.Email, subject, body) + +} + +func (s *EmailService) SendWelcomeEmail(user *models.User, membership *models.Membership) error { + // Prepare data to be injected into the template + data := struct { + FirstName string + MembershipModel string + MembershipID int64 + MembershipFee float32 + RentalFee float32 + }{ + FirstName: user.FirstName, + MembershipModel: membership.Model, + MembershipID: membership.ID, + MembershipFee: float32(membership.MonthlyFee), + RentalFee: float32(membership.RentalFee), } subject := "Willkommen beim Dörpsmobil Hasloh e.V." diff --git a/internal/services/membership_service.go b/internal/services/membership_service.go new file mode 100644 index 0000000..1f6cbec --- /dev/null +++ b/internal/services/membership_service.go @@ -0,0 +1,29 @@ +package services + +import ( + "GoMembership/internal/models" + "GoMembership/internal/repositories" + "time" +) + +type MembershipService interface { + RegisterMembership(membership *models.Membership) (int64, error) + FindMembershipByUserID(userID int64) (*models.Membership, error) +} + +type membershipService struct { + repo repositories.MembershipRepository +} + +func NewMembershipService(repo repositories.MembershipRepository) MembershipService { + return &membershipService{repo} +} + +func (service *membershipService) RegisterMembership(membership *models.Membership) (int64, error) { + membership.StartDate = time.Now() + return service.repo.CreateMembership(membership) +} + +func (service *membershipService) FindMembershipByUserID(userID int64) (*models.Membership, error) { + return service.repo.FindMembershipByUserID(userID) +} diff --git a/internal/services/user_service.go b/internal/services/user_service.go index f2b32d0..95451f5 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -3,7 +3,9 @@ package services import ( "GoMembership/internal/models" "GoMembership/internal/repositories" - // "GoMembership/pkg/errors" + "GoMembership/internal/utils" + "GoMembership/pkg/logger" + // "crypto/rand" // "encoding/base64" // "golang.org/x/crypto/bcrypt" @@ -11,8 +13,9 @@ import ( ) type UserService interface { - RegisterUser(user *models.User) (int64, error) - // AuthenticateUser(email, password string) (*models.User, error) + RegisterUser(user *models.User) (int64, string, error) + // AuthenticateUser(email, password string) (*models.User, error)A + VerifyUser(token *string) (*models.User, error) } type userService struct { @@ -23,7 +26,7 @@ func NewUserService(repo repositories.UserRepository) UserService { return &userService{repo} } -func (service *userService) RegisterUser(user *models.User) (int64, 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 @@ -35,16 +38,45 @@ func (service *userService) RegisterUser(user *models.User) (int64, error) { return -1, err } user.Password = string(hashedPassword) */ - user.Password = "" + user.Status = "unverified" user.CreatedAt = time.Now() user.UpdatedAt = time.Now() - return service.repo.CreateUser(user) + id, err := service.repo.CreateUser(user) + if err != nil { + return -1, "", err + } + user.ID = id + + token, err := utils.GenerateVerificationToken() + if err != nil { + return -1, "", err + } + + logger.Info.Printf("user: %+v", user) + _, 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) { + userID, err := service.repo.VerifyUserOfToken(token) + if err != nil { + return nil, err + } + user, err := service.repo.FindUserByID(userID) + if err != nil { + return nil, err + } + return user, nil } /* 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 diff --git a/internal/templates/mail_welcome.html b/internal/templates/mail_welcome.html deleted file mode 100644 index 883d321..0000000 --- a/internal/templates/mail_welcome.html +++ /dev/null @@ -1,67 +0,0 @@ - - -
- - -Hallo {{.FirstName}},
-herzlich willkommen beim Dörpsmobil Hasloh e.V.! Vielen Dank für Ihre Registrierung und Ihre Unterstützung unseres Projekts.
-Wir freuen uns, Sie als Mitglied begrüßen zu dürfen und wünschen Ihnen stets eine sichere und angenehme Fahrt mit unseren Fahrzeugen.
-Für weitere Fragen stehen wir Ihnen gerne zur Verfügung:
-Besuchen Sie auch unsere Webseite unter https://carsharing-hasloh.de für weitere Informationen.
-Mit freundlichen Grüßen,
- Ihr Team von Dörpsmobil Hasloh e.V.
-
Ein neues Vereinsmitglied hat sich registriert
-{{.Firstname}} {{.LastName}} hat sich registriert. Hier sind die Daten:
-Mit freundlichen Grüßen,
- der Server
-
|
+
+
+ Moin {{.FirstName}} {{.LastName}} 👋,
+
+
+ herzlich willkommen beim Dörpsmobil Hasloh e.V.! Vielen Dank für
+ Ihre Registrierung und Ihre Unterstützung unseres Projekts.
+
+
+
+ +
+ Um die Registrierung abschließen zu können bestätigen Sie bitte
+ noch Ihre Emailadresse indem Sie hier klicken:
+
+
+
+ Alternativ können Sie auch diesen Link in Ihrem Browser öffnen:
+
+
+ https://carsharing-hasloh/backend/api/verify?token={{.Token}}
+
+
+ Nachdem wir Ihre E-Mail Adresse bestätigen konnten, schicken wir
+ Ihnen alle weiteren Informationen zu. Wir freuen uns auf die
+ gemeinsame Zeit mit Ihnen!
+
+
+
+ +
+ Sollte es Probleme geben, möchten wir uns gerne jetzt schon
+ dafür entschuldigen, wenden Sie sich gerne an uns, wir werden
+ uns sofort darum kümmern, versprochen! Antworten Sie einfach auf
+ diese E-Mail.
+
+
+
+ +
+ Mit Freundlichen Grüßen,
+
+
+ Der Vorstand
+
+
+
+ |
+
Hallo {{.FirstName}},
-herzlich willkommen beim Dörpsmobil Hasloh e.V.! Vielen Dank für Ihre Registrierung und Ihre Unterstützung unseres Projekts.
-Wir freuen uns, Sie als Mitglied begrüßen zu dürfen und wünschen Ihnen stets eine sichere und angenehme Fahrt mit unseren Fahrzeugen.
-Für weitere Fragen stehen wir Ihnen gerne zur Verfügung:
-Besuchen Sie auch unsere Webseite unter https://carsharing-hasloh.de für weitere Informationen.
-Mit freundlichen Grüßen,
- Ihr Team von Dörpsmobil Hasloh e.V.
-
|
+
+
+ Moin {{.FirstName}} 👋,
+
+
+ wir freuen uns sehr, dich als neues Mitglied bei Carsharing
+ Hasloh begrüßen zu dürfen! Herzlichen Glückwunsch zur
+ erfolgreichen E-Mail-Verifikation und willkommen in unserem
+ Verein!
+
+
+ Hier einige wichtige Informationen für dich:
+
+
+
+
+ Moqo App Nutzung
+
+
+
+
+ Dörpsmobil SH
+
+
+
+ +
+ Für mehr Informationen besuche gerne unsere Webseite:
+
+
+
+
+ + Solltest du Fragen haben oder Unterstützung benötigen, kannst + du dich jederzeit an unsere Vorsitzende wenden: + +
+ Wir danken dir herzlich für dein Vertrauen in uns und freuen uns
+ darauf, dich hoffentlich bald mit einem Auto begrüßen zu dürfen.
+
+
+
+ Mit freundlichen Grüßen, +Dein Carsharing Hasloh Team + |
+