From 6ac549105379185e2070d8edcac8dda84c4d432a Mon Sep 17 00:00:00 2001 From: "$(pass /github/name)" <$(pass /github/email)> Date: Wed, 10 Jul 2024 14:22:56 +0200 Subject: [PATCH] switched to gorm.. --- configs/config.template.json | 5 +- go.mod | 4 + go.sum | 8 + internal/config/config.go | 11 +- internal/constants/constants.go | 12 ++ internal/controllers/membershipController.go | 60 ++++++ internal/database/db.go | 61 ++---- internal/database/schema.sql | 3 +- internal/models/Verifications.go | 13 ++ internal/models/bank_account.go | 11 +- internal/models/consents.go | 15 +- internal/models/membership.go | 16 +- internal/models/membership_plans.go | 10 - internal/models/subscription_model.go | 18 ++ internal/models/user.go | 52 +++-- internal/repositories/banking_repository.go | 23 +-- internal/repositories/consents_repository.go | 22 +-- .../repositories/membership_repository.go | 46 ++--- .../subscription_model_repository.go | 27 +++ internal/repositories/user_repository.go | 185 +++++++----------- internal/routes/routes.go | 5 +- internal/server/server.go | 18 +- internal/services/bank_account_service.go | 2 +- internal/services/email_service.go | 6 +- internal/services/membership_service.go | 14 +- internal/services/user_service.go | 3 +- pkg/errors/errors.go | 1 + 27 files changed, 368 insertions(+), 283 deletions(-) create mode 100644 internal/constants/constants.go create mode 100644 internal/controllers/membershipController.go create mode 100644 internal/models/Verifications.go delete mode 100644 internal/models/membership_plans.go create mode 100644 internal/models/subscription_model.go create mode 100644 internal/repositories/subscription_model_repository.go diff --git a/configs/config.template.json b/configs/config.template.json index 6643eaa..8ceaadb 100644 --- a/configs/config.template.json +++ b/configs/config.template.json @@ -1,6 +1,6 @@ { "db": { - "DBPath": "data/db.sqlite3" + "Path": "data/db.sqlite3" }, "smtp": { "Host": "mail.server.com", @@ -12,5 +12,8 @@ }, "templates": { "MailDir": "templates/email" + }, + "auth": { + "APIKey": "" } } diff --git a/go.mod b/go.mod index 2801dc3..bb7eec4 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,10 @@ require ( ) require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect + gorm.io/driver/sqlite v1.5.6 // indirect + gorm.io/gorm v1.25.10 // indirect ) diff --git a/go.sum b/go.sum index 2640a1b..834d17e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,10 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +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= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= @@ -10,3 +14,7 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gG gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= +gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE= +gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/internal/config/config.go b/internal/config/config.go index ad5233a..9295677 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,12 +10,13 @@ import ( ) type DatabaseConfig struct { - DBPath string `json:"DBPath"` + Path string `json:"Path"` } type AuthenticationConfig struct { JWTSecret string CSRFSecret string + APIKEY string `json:"APIKey"` } type SMTPConfig struct { @@ -31,10 +32,10 @@ type TemplateConfig struct { MailDir string `json:"MailDir"` } type Config struct { - DB DatabaseConfig `json:"db"` - Auth AuthenticationConfig - SMTP SMTPConfig `json:"smtp"` - Templates TemplateConfig `json:"templates"` + Auth AuthenticationConfig `json:"auth"` + DB DatabaseConfig `json:"db"` + Templates TemplateConfig `json:"templates"` + SMTP SMTPConfig `json:"smtp"` } var ( diff --git a/internal/constants/constants.go b/internal/constants/constants.go new file mode 100644 index 0000000..a26fd69 --- /dev/null +++ b/internal/constants/constants.go @@ -0,0 +1,12 @@ +package constants + +const ( + UnverifiedStatus = iota + 1 + VerifiedStatus + ActiveStatus + PassiveStatus + DisabledStatus + DelayedPaymentStatus + SettledPaymentStatus + AwaitingPaymentStatus +) diff --git a/internal/controllers/membershipController.go b/internal/controllers/membershipController.go new file mode 100644 index 0000000..047e14f --- /dev/null +++ b/internal/controllers/membershipController.go @@ -0,0 +1,60 @@ +package controllers + +import ( + "encoding/json" + + "GoMembership/internal/config" + "GoMembership/internal/models" + "GoMembership/internal/services" + + // "github.com/gorilla/mux" + "net/http" + // "strconv" + "GoMembership/internal/utils" + "GoMembership/pkg/logger" +) + +type MembershipController struct { + service services.MembershipService +} +type MembershipData struct { + APIKey string + Model models.SubscriptionModel +} + +func NewMembershipController(service services.MembershipService) *MembershipController { + return &MembershipController{service} +} + +func (uc *MembershipController) RegisterPlan(w http.ResponseWriter, r *http.Request) { + rh := utils.NewResponseHandler(w) + var regData MembershipData + + if err := json.NewDecoder(r.Body).Decode(®Data); err != nil { + logger.Error.Printf("Couldn't decode SubscriptionModel: %v", err) + rh.RespondWithError(http.StatusBadRequest, "Couldn't decode Membershipmodel") + return + } + logger.Info.Printf("Using API key: %v", config.LoadConfig().Auth.APIKEY) + if regData.APIKey == "" { + logger.Error.Println("API Key is missing") + rh.RespondWithError(http.StatusBadRequest, "API Key is required") + return + } + if regData.APIKey != config.LoadConfig().Auth.APIKEY { + logger.Error.Printf("API Key not valid: %v", regData.APIKey) + rh.RespondWithError(http.StatusExpectationFailed, "API Key not valid") + return + } + logger.Info.Printf("registering plan: %+v", regData) + + // Register Plan + id, err := uc.service.RegisterPlan(®Data.Model) + if err != nil { + logger.Error.Printf("Couldn't register Membershipmodel: %v", err) + rh.RespondWithError(http.StatusInternalServerError, "Couldn't register Membershipmodel") + return + } + regData.Model.ID = id + +} diff --git a/internal/database/db.go b/internal/database/db.go index 7895c1c..2180f2a 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -1,55 +1,30 @@ package database import ( - "database/sql" - "os" - - "GoMembership/internal/config" + "GoMembership/internal/models" "GoMembership/pkg/logger" - - "embed" - - _ "github.com/mattn/go-sqlite3" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) -//go:embed schema.sql -var fs embed.FS +// var DB *gorm.DB -func initializeDB(dbPath string) error { +func InitDB(dbPath string) (*gorm.DB, error) { - db, err := sql.Open("sqlite3", dbPath) + db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { - return err + return nil, err } - defer db.Close() - - schema, err := fs.ReadFile("schema.sql") - if err != nil { - return err + if err := db.AutoMigrate( + &models.User{}, + &models.SubscriptionModel{}, + &models.Membership{}, + &models.Consent{}, + &models.Verification{}, + &models.BankAccount{}); err != nil { + logger.Error.Fatalf("Couldn't create database: %v", err) + return nil, err } - _, err = db.Exec(string(schema)) - if err != nil { - return err - } - return nil -} - -func Connect(DBcfg config.DatabaseConfig) *sql.DB { - _, err := os.Stat(DBcfg.DBPath) - if os.IsNotExist(err) { - initErr := initializeDB(DBcfg.DBPath) - if initErr != nil { - logger.Error.Fatalf("Couldn't create database: %v", initErr) - } - logger.Info.Println("Created new database") - } - - db, err := sql.Open("sqlite3", DBcfg.DBPath) - if err != nil { - logger.Error.Fatal(err) - } - if err := db.Ping(); err != nil { - logger.Error.Fatal(err) - } - return db + // DB = db + return db, nil } diff --git a/internal/database/schema.sql b/internal/database/schema.sql index 2d44a83..c1ab65a 100644 --- a/internal/database/schema.sql +++ b/internal/database/schema.sql @@ -48,9 +48,8 @@ CREATE TABLE membership_plans ( 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, + conditions TEXT, details TEXT ); diff --git a/internal/models/Verifications.go b/internal/models/Verifications.go new file mode 100644 index 0000000..f52fd67 --- /dev/null +++ b/internal/models/Verifications.go @@ -0,0 +1,13 @@ +package models + +import "time" + +type Verification struct { + UpdatedAt time.Time + CreatedAt time.Time + EmailVerifiedAt *time.Time `gorm:"Default:NULL" json:"email_verified_at"` + IDVerifiedAt *time.Time `gorm:"Default:NULL" json:"id_verified_at"` + VerificationToken string `json:"token"` + ID int64 `gorm:"primaryKey"` + UserID int64 `gorm:"unique;" json:"user_id"` +} diff --git a/internal/models/bank_account.go b/internal/models/bank_account.go index 012ae4f..0e29606 100644 --- a/internal/models/bank_account.go +++ b/internal/models/bank_account.go @@ -3,11 +3,14 @@ package models import "time" type BankAccount struct { - MandateDateSigned time.Time `json:"mandate_date_signed"` + 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 `json:"iban"` + IBAN string `gorm:"not null" json:"iban"` BIC string `json:"bic"` - MandateReference string `json:"mandate_reference"` - UserID int `json:"id"` + MandateReference string `gorm:"not null" json:"mandate_reference"` + ID int64 `gorm:"primaryKey"` + UserID int64 `json:"user_id"` } diff --git a/internal/models/consents.go b/internal/models/consents.go index 584968d..dc5dc60 100644 --- a/internal/models/consents.go +++ b/internal/models/consents.go @@ -3,11 +3,12 @@ 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"` + CreatedAt time.Time + UpdatedAt time.Time + FirstName string `gorm:"not null" json:"first_name"` + LastName string `gorm:"not null" json:"last_name"` + Email string `gorm:"unique" json:"email"` + ConsentType string `gorm:"not null" json:"consent_type"` + ID int64 `gorm:"primaryKey"` + UserID int64 `gorm:"not null" json:"user_id"` } diff --git a/internal/models/membership.go b/internal/models/membership.go index 1a5e60f..c7041f4 100644 --- a/internal/models/membership.go +++ b/internal/models/membership.go @@ -3,10 +3,14 @@ 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"` + CreatedAt time.Time + UpdatedAt time.Time + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + Status string `json:"status"` + SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID"` + SubscriptionModelID int64 `json:"subsription_model_id"` + 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 deleted file mode 100644 index 321fd12..0000000 --- a/internal/models/membership_plans.go +++ /dev/null @@ -1,10 +0,0 @@ -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/subscription_model.go b/internal/models/subscription_model.go new file mode 100644 index 0000000..915cd4f --- /dev/null +++ b/internal/models/subscription_model.go @@ -0,0 +1,18 @@ +package models + +import ( + "time" +) + +type SubscriptionModel struct { + CreatedAt time.Time + UpdatedAt time.Time + Name string `json:"name"` + Details string `json:"details"` + Conditions string `json:"conditions"` + 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"` +} diff --git a/internal/models/user.go b/internal/models/user.go index 779235a..fbdf710 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -1,23 +1,39 @@ package models -import "time" +import ( + "GoMembership/internal/constants" + "gorm.io/gorm" + "time" +) type User struct { - 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"` + UpdatedAt time.Time + DateOfBirth time.Time `gorm:"not null" json:"date_of_birth"` + CreatedAt time.Time + Salt *string `json:"-"` + Phone *string `json:"phone"` + Notes *string `json:"notes"` + FirstName string `gorm:"not null" json:"first_name"` + 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"` + Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"` + BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` + ID int64 `gorm:"primaryKey"` + PaymentStatus int8 `json:"payment_status"` + Status int8 `json:"status"` + RoleID int8 `json:"role_id"` +} + +// BeforeCreate sets the default value for Status +func (u *User) BeforeCreate(tx *gorm.DB) (err error) { + if u.Status == 0 { // Assuming 0 is an unset value + u.Status = constants.UnverifiedStatus + u.PaymentStatus = constants.AwaitingPaymentStatus + } + return } diff --git a/internal/repositories/banking_repository.go b/internal/repositories/banking_repository.go index eaa78b0..669412d 100644 --- a/internal/repositories/banking_repository.go +++ b/internal/repositories/banking_repository.go @@ -2,8 +2,7 @@ package repositories import ( "GoMembership/internal/models" - // "GoMembership/pkg/errors" - "database/sql" + "gorm.io/gorm" ) type BankAccountRepository interface { @@ -11,25 +10,17 @@ type BankAccountRepository interface { } type bankAccountRepository struct { - db *sql.DB + db *gorm.DB } -func NewBankAccountRepository(db *sql.DB) BankAccountRepository { +func NewBankAccountRepository(db *gorm.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 + result := repo.db.Create(account) + if result.Error != nil { + return 0, result.Error } - lastInsertID, err := result.LastInsertId() - if err != nil { - return -1, err - } - - return lastInsertID, err + return account.ID, nil } diff --git a/internal/repositories/consents_repository.go b/internal/repositories/consents_repository.go index d95bc67..0afa468 100644 --- a/internal/repositories/consents_repository.go +++ b/internal/repositories/consents_repository.go @@ -2,8 +2,7 @@ package repositories import ( "GoMembership/internal/models" - // "GoMembership/pkg/errors" - "database/sql" + "gorm.io/gorm" ) type ConsentRepository interface { @@ -11,25 +10,18 @@ type ConsentRepository interface { } type consentRepository struct { - db *sql.DB + db *gorm.DB } -func NewConsentRepository(db *sql.DB) ConsentRepository { +func NewConsentRepository(db *gorm.DB) ConsentRepository { return &consentRepository{db} } func (repo *consentRepository) CreateConsent(consent *models.Consent) (int64, error) { + result := repo.db.Create(consent) - 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 + if result.Error != nil { + return 0, result.Error } - lastInsertID, err := result.LastInsertId() - if err != nil { - return -1, err - } - - return lastInsertID, err + return consent.ID, nil } diff --git a/internal/repositories/membership_repository.go b/internal/repositories/membership_repository.go index 0f11d91..2323539 100644 --- a/internal/repositories/membership_repository.go +++ b/internal/repositories/membership_repository.go @@ -1,53 +1,41 @@ package repositories import ( - "database/sql" - "time" + "gorm.io/gorm" "GoMembership/internal/models" - "GoMembership/pkg/errors" ) type MembershipRepository interface { - CreateMembership(account *models.Membership) (int64, error) + CreateMembership(membership *models.Membership) (int64, error) FindMembershipByUserID(userID int64) (*models.Membership, error) } type membershipRepository struct { - db *sql.DB + db *gorm.DB } -func NewMembershipRepository(db *sql.DB) MembershipRepository { +func NewMembershipRepository(db *gorm.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 + result := repo.db.Create(membership) + if result.Error != nil { + return 0, result.Error } - lastInsertID, err := result.LastInsertId() - if err != nil { - return -1, err - } - - return lastInsertID, err + return membership.ID, nil } 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 + var membership models.Membership + result := repo.db.First(&membership, userID) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + return nil, gorm.ErrRecordNotFound + } + return nil, result.Error + } + return &membership, nil } diff --git a/internal/repositories/subscription_model_repository.go b/internal/repositories/subscription_model_repository.go new file mode 100644 index 0000000..67df4c7 --- /dev/null +++ b/internal/repositories/subscription_model_repository.go @@ -0,0 +1,27 @@ +package repositories + +import ( + "gorm.io/gorm" + + "GoMembership/internal/models" +) + +type SubscriptionModelsRepository interface { + CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (int64, error) +} + +type subscriptionModelsRepository struct { + db *gorm.DB +} + +func NewSubscriptionModelsRepository(db *gorm.DB) SubscriptionModelsRepository { + return &subscriptionModelsRepository{db} +} + +func (repo *subscriptionModelsRepository) CreateSubscriptionModel(subscriptionModel *models.SubscriptionModel) (int64, error) { + result := repo.db.Create(subscriptionModel) + if result.Error != nil { + return 0, result.Error + } + return subscriptionModel.ID, nil +} diff --git a/internal/repositories/user_repository.go b/internal/repositories/user_repository.go index fbe1917..4ef4311 100644 --- a/internal/repositories/user_repository.go +++ b/internal/repositories/user_repository.go @@ -1,12 +1,14 @@ package repositories import ( + "GoMembership/internal/constants" + "time" + + "gorm.io/gorm" + "GoMembership/internal/models" "GoMembership/pkg/errors" - "database/sql" - "fmt" - "strings" - "time" + "gorm.io/gorm/clause" ) type UserRepository interface { @@ -19,63 +21,32 @@ type UserRepository interface { } type userRepository struct { - db *sql.DB + db *gorm.DB } -func NewUserRepository(db *sql.DB) UserRepository { +func NewUserRepository(db *gorm.DB) UserRepository { return &userRepository{db} } func (repo *userRepository) CreateUser(user *models.User) (int64, error) { - 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 + result := repo.db.Create(user) + if result.Error != nil { + return 0, result.Error } - - lastInsertID, err := result.LastInsertId() - if err != nil { - return -1, err - } - return lastInsertID, err + return user.ID, nil } 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) + result := repo.db.Session(&gorm.Session{FullSaveAssociations: true}).Model(&models.User{}).Where("id = ?", userID).Updates(&updates) + if result.Error != nil { + return result.Error } - 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 + if result.RowsAffected == 0 { + return errors.ErrNoRowsAffected } return nil @@ -83,88 +54,76 @@ func (repo *userRepository) UpdateUser(userID int64, updates map[string]interfac func (repo *userRepository) FindUserByID(id int64) (*models.User, error) { var user models.User - 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 + result := repo.db.First(&user, id) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + return nil, gorm.ErrRecordNotFound } - return nil, err + return nil, result.Error } return &user, nil } func (repo *userRepository) FindUserByEmail(email string) (*models.User, error) { var user models.User - 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 + result := repo.db.Where("email = ?", email).First(&user) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + return nil, gorm.ErrRecordNotFound } - return nil, err + return nil, result.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 + var user models.User + result := repo.db.Select("status").First(&user, userID) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + return false, gorm.ErrRecordNotFound } - return false, err + return false, result.Error } - return status != "unverified", nil + return user.Status != constants.UnverifiedStatus, 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", + var emailVerification models.Verification + result := repo.db.Where("verification_token = ?", token).First(&emailVerification) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + return 0, gorm.ErrRecordNotFound + } + return 0, result.Error } - err = repo.UpdateUser(userID, update) + // Check if the user is already verified + verified, err := repo.IsVerified(&emailVerification.UserID) if err != nil { - return -1, err + return 0, err } - query := "UPDATE email_verifications SET verified_at = ? WHERE user_id = ?" - _, err = repo.db.Exec(query, time.Now(), userID) + if verified { + return emailVerification.UserID, gorm.ErrRecordNotFound + } + + // Update user status to active + t := time.Now() + emailVerification.EmailVerifiedAt = &t + update := map[string]interface{}{ + "status": constants.VerifiedStatus, + "verifications": emailVerification, + } + err = repo.UpdateUser(emailVerification.UserID, update) if err != nil { - return -1, err + return 0, err } - return userID, nil + + return emailVerification.UserID, nil } func (repo *userRepository) SetVerificationToken(user *models.User, token *string) (int64, error) { - // check if user already verified + // Check if user is already verified verified, err := repo.IsVerified(&user.ID) if err != nil { return -1, err @@ -173,15 +132,21 @@ func (repo *userRepository) SetVerificationToken(user *models.User, token *strin 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 + // Prepare the Verification record + verification := models.Verification{ + UserID: user.ID, + VerificationToken: *token, } - return lastInsertID, nil + // Use GORM to insert or update the Verification record + result := repo.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "user_id"}}, + DoUpdates: clause.AssignmentColumns([]string{"verification_token", "created_at"}), + }).Create(&verification) + + if result.Error != nil { + return -1, result.Error + } + + return verification.ID, nil } diff --git a/internal/routes/routes.go b/internal/routes/routes.go index f81b7ed..f727642 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -9,10 +9,11 @@ import ( "github.com/gorilla/mux" ) -func RegisterRoutes(router *mux.Router, userController *controllers.UserController) { - logger.Info.Println("Registering /api/register route") +func RegisterRoutes(router *mux.Router, userController *controllers.UserController, membershipController *controllers.MembershipController) { + logger.Info.Println("Registering backend/api/register route") router.HandleFunc("/backend/api/verify", userController.VerifyMailHandler).Methods("GET") router.HandleFunc("/backend/api/register", userController.RegisterUser).Methods("POST") + router.HandleFunc("/backend/api/plans", membershipController.RegisterPlan).Methods("POST") // router.HandleFunc("/login", userController.LoginUser).Methods("POST") } diff --git a/internal/server/server.go b/internal/server/server.go index 7748ef7..d864c63 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,6 +1,8 @@ package server import ( + "net/http" + "GoMembership/internal/config" "GoMembership/internal/controllers" "GoMembership/internal/database" @@ -9,16 +11,17 @@ import ( "GoMembership/internal/routes" "GoMembership/internal/services" "GoMembership/pkg/logger" - "net/http" "github.com/gorilla/mux" ) func Run() { cfg := config.LoadConfig() - logger.Info.Printf("Config: %v", cfg) - db := database.Connect(cfg.DB) - defer db.Close() + logger.Info.Printf("Config: %+v", cfg) + db, 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) @@ -26,11 +29,12 @@ func Run() { bankAccountRepo := repositories.NewBankAccountRepository(db) bankAccountService := services.NewBankAccountService(bankAccountRepo) membershipRepo := repositories.NewMembershipRepository(db) - membershipService := services.NewMembershipService(membershipRepo) + planRepo := repositories.NewSubscriptionModelsRepository(db) + membershipService := services.NewMembershipService(membershipRepo, planRepo) userRepo := repositories.NewUserRepository(db) userService := services.NewUserService(userRepo) userController := controllers.NewUserController(userService, emailService, consentService, bankAccountService, membershipService) - + membershipController := controllers.NewMembershipController(membershipService) router := mux.NewRouter() // router.Handle("/csrf-token", middlewares.GenerateCSRFTokenHandler()).Methods("GET") @@ -38,7 +42,7 @@ func Run() { // router.Use(middlewares.CSRFMiddleware) router.Use(middlewares.LoggerMiddleware) - routes.RegisterRoutes(router, userController) + routes.RegisterRoutes(router, userController, membershipController) // create subrouter for teh authenticated area /account // also pthprefix matches everything below /account // accountRouter := router.PathPrefix("/account").Subrouter() diff --git a/internal/services/bank_account_service.go b/internal/services/bank_account_service.go index d28a902..92f880c 100644 --- a/internal/services/bank_account_service.go +++ b/internal/services/bank_account_service.go @@ -24,7 +24,7 @@ func NewBankAccountService(repo repositories.BankAccountRepository) BankAccountS func (service *bankAccountService) RegisterBankAccount(bankAccount *models.BankAccount) (int64, error) { bankAccount.MandateDateSigned = time.Now() - ref, err := generateSEPAMandateReference(strconv.Itoa(bankAccount.UserID)) + ref, err := generateSEPAMandateReference(strconv.FormatInt(bankAccount.UserID, 10)) if err != nil { return -1, err } diff --git a/internal/services/email_service.go b/internal/services/email_service.go index 5598bee..9b47d55 100644 --- a/internal/services/email_service.go +++ b/internal/services/email_service.go @@ -86,10 +86,10 @@ func (s *EmailService) SendWelcomeEmail(user *models.User, membership *models.Me RentalFee float32 }{ FirstName: user.FirstName, - MembershipModel: membership.Model, + MembershipModel: membership.SubscriptionModel.Name, MembershipID: membership.ID, - MembershipFee: float32(membership.MonthlyFee), - RentalFee: float32(membership.RentalFee), + MembershipFee: float32(membership.SubscriptionModel.MonthlyFee), + RentalFee: float32(membership.SubscriptionModel.HourlyRate), } subject := "Willkommen beim Dörpsmobil Hasloh e.V." diff --git a/internal/services/membership_service.go b/internal/services/membership_service.go index 1f6cbec..1d9227d 100644 --- a/internal/services/membership_service.go +++ b/internal/services/membership_service.go @@ -9,14 +9,16 @@ import ( type MembershipService interface { RegisterMembership(membership *models.Membership) (int64, error) FindMembershipByUserID(userID int64) (*models.Membership, error) + RegisterPlan(plan *models.SubscriptionModel) (int64, error) } type membershipService struct { - repo repositories.MembershipRepository + repo repositories.MembershipRepository + planRepo repositories.SubscriptionModelsRepository } -func NewMembershipService(repo repositories.MembershipRepository) MembershipService { - return &membershipService{repo} +func NewMembershipService(repo repositories.MembershipRepository, planRepo repositories.SubscriptionModelsRepository) MembershipService { + return &membershipService{repo, planRepo} } func (service *membershipService) RegisterMembership(membership *models.Membership) (int64, error) { @@ -27,3 +29,9 @@ func (service *membershipService) RegisterMembership(membership *models.Membersh func (service *membershipService) FindMembershipByUserID(userID int64) (*models.Membership, error) { return service.repo.FindMembershipByUserID(userID) } + +// Membership_Plans + +func (service *membershipService) RegisterPlan(plan *models.SubscriptionModel) (int64, error) { + return service.planRepo.CreateSubscriptionModel(plan) +} diff --git a/internal/services/user_service.go b/internal/services/user_service.go index 95451f5..4111755 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -1,6 +1,7 @@ package services import ( + "GoMembership/internal/constants" "GoMembership/internal/models" "GoMembership/internal/repositories" "GoMembership/internal/utils" @@ -38,7 +39,7 @@ func (service *userService) RegisterUser(user *models.User) (int64, string, erro return -1, err } user.Password = string(hashedPassword) */ - user.Status = "unverified" + user.Status = constants.UnverifiedStatus user.CreatedAt = time.Now() user.UpdatedAt = time.Now() id, err := service.repo.CreateUser(user) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 3ba2130..f133876 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -11,4 +11,5 @@ var ( ErrTokenNotFound = errors.New("verification token not found") ErrTokenNotSet = errors.New("verification token has not been set") ErrNoData = errors.New("no data provided") + ErrNoRowsAffected = errors.New("no rows affected") )