From 72ccd5e71c5fa034a7766c3de6f19874c96f33f0 Mon Sep 17 00:00:00 2001 From: Nedim Date: Mon, 30 Oct 2023 19:21:43 +0100 Subject: [PATCH] Added reset password route --- config/config.go | 4 + config/models.go | 6 + controllers/users_controller.go | 113 +++++++++++++++ database/user/user.go | 42 ++++++ env-example | 4 +- go.mod | 8 +- go.sum | 36 ++++- main.go | 32 +++-- models/company.go | 14 ++ models/notification.go | 11 +- models/password_tokens.go | 18 +++ models/user.go | 9 ++ postman/NOVA.postman_collection.json | 180 ++++++++++++++++++++++++ routes/public_routes.go | 4 + services/messaging/messaging_service.go | 75 +++++++++- shared/database.go | 8 +- 16 files changed, 531 insertions(+), 33 deletions(-) create mode 100644 controllers/users_controller.go create mode 100644 database/user/user.go create mode 100644 models/password_tokens.go diff --git a/config/config.go b/config/config.go index fcc9bea..f6f9e7b 100644 --- a/config/config.go +++ b/config/config.go @@ -44,6 +44,10 @@ func Load() error { ContractAddress: getEnv("NOVATECH_BLOCKCHAIN_CONTRACT_ADDRESS", ""), WalletAddress: getEnv("NOVATECH_BLOCKCHAIN_WALLET_ADDRESS", ""), WalletPrivateKey: getEnv("NOVATECH_BLOCKCHAIN_WALLET_PRIVATE_KEY", ""), + }, + AWS: AWS { + AccessKey: getEnv("AWS_ACCESS_KEY_ID", ""), + SecretKey: getEnv("AWS_SECRET_ACCESS_KEY", ""), }, } diff --git a/config/models.go b/config/models.go index d04ac26..bf17691 100644 --- a/config/models.go +++ b/config/models.go @@ -6,6 +6,7 @@ type Config struct { AdminService Service Database Database Blockchain Blockchain + AWS AWS } // Service contains configuration for service @@ -32,3 +33,8 @@ type Database struct { HostName string Port string } + +type AWS struct { + AccessKey string + SecretKey string +} diff --git a/controllers/users_controller.go b/controllers/users_controller.go new file mode 100644 index 0000000..dbefece --- /dev/null +++ b/controllers/users_controller.go @@ -0,0 +1,113 @@ +package controllers + +import ( + "crypto/rand" + "encoding/base64" + "net/http" + + "github.com/gin-gonic/gin" + "gitlab.com/pactual1/backend/database/user" + "gitlab.com/pactual1/backend/models" + "gitlab.com/pactual1/backend/services/messaging" + "gitlab.com/pactual1/backend/shared" + "golang.org/x/crypto/bcrypt" +) + +func ResetPassword(c *gin.Context) { + var req models.ResetPasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbUser, err := user.GetUserByEmail(req.Email) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + resetToken, err := GenerateResetToken() + + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + err = user.SaveResetTokenToDB(dbUser.ID, resetToken) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + subject := "Password Reset Request" + body := "Here is your password reset link: https://pactualdev.com/setNewPassword?token=" + resetToken + email := models.EmailNotification{Body: body, Subject: subject, Email: dbUser.Email} + + go func(email models.EmailNotification) { + + emailChannel := messaging.GetEmailChannel() + emailChannel <- email + + }(email) + + c.JSON(http.StatusOK, gin.H{"message": "Reset email sent"}) +} + +func GenerateResetToken() (string, error) { + // Generate 32 random bytes (256 bits) + randomBytes := make([]byte, 32) + _, err := rand.Read(randomBytes) + if err != nil { + return "", err // return an error if there was one + } + + // Encode the random bytes into a URL-safe base64 string + resetToken := base64.URLEncoding.EncodeToString(randomBytes) + + return resetToken, nil +} + +func UpdatePassword(c *gin.Context) { + var req models.UpdatePasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Find the PasswordTokens entry + var passwordToken models.PasswordTokens + if err := shared.GetDb().Where("token = ?", req.Token).First(&passwordToken).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Invalid token"}) + return + } + + // Find the associated User + var user models.User + if err := shared.GetDb().Where("id = ?", passwordToken.UserID).First(&user).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + // Hash the password before saving it + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Update the user's password and set them as active + user.Password = string(hashedPassword) + user.IsActive = true + if err := shared.GetDb().Save(&user).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Delete the PasswordTokens entry + if err := shared.GetDb().Delete(&passwordToken).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"}) +} diff --git a/database/user/user.go b/database/user/user.go new file mode 100644 index 0000000..b39df5c --- /dev/null +++ b/database/user/user.go @@ -0,0 +1,42 @@ +package user + +import ( + "time" + + "github.com/jinzhu/gorm" + "gitlab.com/pactual1/backend/models" + "gitlab.com/pactual1/backend/shared" +) + +func SaveResetTokenToDB(userID uint, resetToken string) error { + // Calculate the expiry date (one month from now) + expiryDate := time.Now().AddDate(0, 1, 0).Format(time.RFC3339) + + // Create a new PasswordTokens instance + passwordToken := models.PasswordTokens{ + UserID: userID, + Token: resetToken, + ExpiryDate: expiryDate, + } + + // Save the password token to the database + if err := shared.GetDb().Create(&passwordToken).Error; err != nil { + return err + } + + return nil +} + +func GetUserByEmail(email string) (*models.User, error) { + var user models.User + + // Query the database for a user with the specified email + if err := shared.GetDb().Where("email = ?", email).First(&user).Error; err != nil { + if gorm.IsRecordNotFoundError(err) { + return nil, nil + } + return nil, err + } + + return &user, nil +} \ No newline at end of file diff --git a/env-example b/env-example index 2724428..3deee8b 100644 --- a/env-example +++ b/env-example @@ -12,4 +12,6 @@ NOVATECH_BLOCKCHAIN_NETWORK_ENDPOINT="https://polygon-mumbai.infura.io/v3/4458cf NOVATECH_BLOCKCHAIN_CONTRACT_ADDRESS=0x121Cb4bFEeDb55d598D8F5e9EeDF8bB14c421d96 NOVATECH_BLOCKCHAIN_WALLET_ADDRESS=0x20eff5decaed29bd64f0c6385956363eeaaf4d3e NOVATECH_BLOCKCHAIN_WALLET_PRIVATE_KEY=PRIVATE_KEY -NOVATECH_SERVICE_MAPBOX_ACCESS_TOKEN=pk.ey \ No newline at end of file +NOVATECH_SERVICE_MAPBOX_ACCESS_TOKEN=pk.ey +AWS_ACCESS_KEY_ID:access +AWS_SECRET_ACCESS_KEY:secret \ No newline at end of file diff --git a/go.mod b/go.mod index 2d4aef7..924c617 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitlab.com/pactual1/backend go 1.21 require ( + github.com/aws/aws-sdk-go v1.46.6 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/ethereum/go-ethereum v1.13.1 github.com/gin-gonic/gin v1.9.1 @@ -66,6 +67,7 @@ require ( github.com/holiman/uint256 v1.2.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.0 // indirect @@ -106,12 +108,12 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect golang.org/x/arch v0.5.0 // indirect - golang.org/x/crypto v0.13.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index bd0af66..212d95c 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9Pq github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-sdk-go v1.34.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.46.6 h1:6wFnNC9hETIZLMf6SOTN7IcclrOGwp/n9SLp8Pjt6E8= +github.com/aws/aws-sdk-go v1.46.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -169,6 +171,10 @@ github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -343,6 +349,7 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRT github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yosssi/gohtml v0.0.0-20200519115854-476f5b4b8047 h1:YWaOkupKL+BRRJSWRq/uhSkWXc1K0QVIYVG36XUBGOc= github.com/yosssi/gohtml v0.0.0-20200519115854-476f5b4b8047/go.mod h1:+ccdNT0xMY1dtc5XBxumbYfOUhmduiGudqaDgD2rVRE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -351,24 +358,32 @@ golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -382,26 +397,34 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= @@ -413,6 +436,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 149c6e4..4bc82a0 100644 --- a/main.go +++ b/main.go @@ -37,22 +37,35 @@ func main() { Admin := admin.New(&admin.AdminConfig{DB: shared.GetDb()}) Admin.RegisterViewPath("app/views/qor") fmt.Printf("Admin instance: %+v\n", Admin) + // Allow Admin to manage User resource company := Admin.AddResource(&models.Company{}) - company.Meta(&admin.Meta{Name: "Users", Config: &admin.SelectManyConfig{SelectMode: "bottom_sheet"}}) - company.Meta(&admin.Meta{Name: "Devices", Config: &admin.SelectManyConfig{SelectMode: "bottom_sheet"}}) - // company.Meta(&admin.Meta{Name: "DeviceInfos", Config: &admin.SelectManyConfig{SelectMode: "bottom_sheet"}}) - // Add User and Device resources Admin.AddResource(&models.User{}) Admin.AddResource(&models.Device{}) - Admin.AddResource(&models.Company{}) Admin.AddResource(&models.ProductTemplate{}) - // Initialize HTTP request multiplexer + company.Meta(&admin.Meta{Name: "Users", Config: &admin.SelectManyConfig{SelectMode: "select"}}) + company.Meta(&admin.Meta{Name: "Devices", Config: &admin.SelectManyConfig{SelectMode: "bottom_sheet"}}) + // // Initialize HTTP request multiplexe mux := http.NewServeMux() - // Mount admin interface to mux + // // Mount admin interface to mux Admin.MountTo("/admin", mux) + // // Setup Admin + // Admin := admin.New(&admin.AdminConfig{DB: shared.GetDb()}) + // Admin.AddResource(&models.Student{}) + // school := Admin.AddResource(&models.School{}) + // school.Meta(&admin.Meta{Name: "Students", Config: &admin.SelectManyConfig{SelectMode: "select"}}) + // Admin.MountTo("/admin", mux) + + // school.SaveHandler = func(value interface{}, ctx *qor.Context) error { + // schoolRecord := value.(*models.School) + // log.Printf("Executed handler %v", schoolRecord) + // log.Printf("Executed handler %v", schoolRecord.Students) + + // return ctx.GetDB().Save(schoolRecord).Error + // } + // Start the admin server in a separate goroutine go func() { port := config.AppConfig.Service.Port @@ -66,10 +79,13 @@ func main() { // Start services and pass the respective channels notificationCh := make(chan models.Notification, 100) + // Start services and pass the respective channels + emailCh := make(chan models.EmailNotification, 100) // Create a new messaging service - messagingService := messaging.NewService(notificationCh, shared.GetDb()) + messagingService := messaging.NewService(notificationCh, emailCh, shared.GetDb()) // Run the messaging service go messagingService.MessagingService() + go messagingService.SendEmailService() go erp.ERPService(erpChannel) // Sending messages via channels diff --git a/models/company.go b/models/company.go index a861586..c26435f 100644 --- a/models/company.go +++ b/models/company.go @@ -22,6 +22,20 @@ type Company struct { Devices []Device `json:"devices"` } +type School struct { + BaseModel + Name string `json:"name"` + Address string `json:"address"` + Students []Student `json:"students" gorm:"foreignKey:SchoolID"` +} + +type Student struct { + BaseModel + Name string `json:"name"` + Age int `json:"age"` + SchoolID uint `json:"schoolId" gorm:"index"` +} + type CompanyShortResponse struct { ID int `json:"id"` Name string `json:"name"` diff --git a/models/notification.go b/models/notification.go index 11046ef..e9ebeb3 100644 --- a/models/notification.go +++ b/models/notification.go @@ -8,12 +8,17 @@ import ( type Notification struct { gorm.Model - Title string + Title string NotificationType string - Text string - CompanyID int + Text string + CompanyID int } +type EmailNotification struct { + Subject string + Email string + Body string +} type GetNotificationsResponse struct { Type string `json:"type"` diff --git a/models/password_tokens.go b/models/password_tokens.go new file mode 100644 index 0000000..0c2a371 --- /dev/null +++ b/models/password_tokens.go @@ -0,0 +1,18 @@ +package models + +type PasswordTokens struct { + BaseModel + UserID uint `json:"username"` + Token string `json:"token"` + ExpiryDate string `json:"expiryDate"` +} + +func (PasswordTokens) Update() (bool, error) { + return false, nil +} +func (PasswordTokens) Create() (bool, error) { + return false, nil +} +func (PasswordTokens) Delete() (bool, error) { + return false, nil +} diff --git a/models/user.go b/models/user.go index ef04325..cb0c209 100644 --- a/models/user.go +++ b/models/user.go @@ -6,9 +6,18 @@ type User struct { Password string `json:"password"` Email string `json:"email"` Avatar string `json:"avatar"` + IsActive bool `json:"isActive" gorm:"default:false"` CompanyID uint `json:"companyId"` } +type ResetPasswordRequest struct { + Email string `json:"email"` +} +type UpdatePasswordRequest struct { + Password string `json:"password"` + Token string `json:"token"` +} + func (User) Update() (bool, error) { return false, nil } diff --git a/postman/NOVA.postman_collection.json b/postman/NOVA.postman_collection.json index 51a35c7..548d246 100644 --- a/postman/NOVA.postman_collection.json +++ b/postman/NOVA.postman_collection.json @@ -1459,6 +1459,186 @@ "body": "{\n \"id\": 9,\n \"message\": \"Successfully received and saved contract\"\n}" } ] + }, + { + "name": "Reset password", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"someemail@gmail.com\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/user/reset/password", + "host": [ + "{{URL}}" + ], + "path": [ + "user", + "reset", + "password" + ] + } + }, + "response": [ + { + "name": "Create Contracts", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"sellerId\": 1,\n \"buyerId\": 2,\n \"description\": \"This is a sample contract.\",\n \"productId\": 3,\n \"minTemp\": -20.0,\n \"maxTemp\": 40.0,\n \"arrivalDate\": 1674019200,\n \"penaltyType\": \"AMOUNT\",\n \"penaltyValue\": 100,\n \"penaltyRec\": \"DAILY\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/contracts/create", + "host": [ + "{{URL}}" + ], + "path": [ + "contracts", + "create" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Fri, 06 Oct 2023 08:40:15 GMT" + }, + { + "key": "Content-Length", + "value": "61" + } + ], + "cookie": [], + "body": "{\n \"id\": 9,\n \"message\": \"Successfully received and saved contract\"\n}" + } + ] + }, + { + "name": "Set password", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"someemail@gmail.com\",\n \"token\": \"sb6qxahXINNsg52dH0Q7u7iR6yaPLRRQ4OnbUWlxEo0=\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/user/set/password", + "host": [ + "{{URL}}" + ], + "path": [ + "user", + "set", + "password" + ] + } + }, + "response": [ + { + "name": "Create Contracts", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"sellerId\": 1,\n \"buyerId\": 2,\n \"description\": \"This is a sample contract.\",\n \"productId\": 3,\n \"minTemp\": -20.0,\n \"maxTemp\": 40.0,\n \"arrivalDate\": 1674019200,\n \"penaltyType\": \"AMOUNT\",\n \"penaltyValue\": 100,\n \"penaltyRec\": \"DAILY\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/contracts/create", + "host": [ + "{{URL}}" + ], + "path": [ + "contracts", + "create" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "POST, OPTIONS, GET, PUT" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Date", + "value": "Fri, 06 Oct 2023 08:40:15 GMT" + }, + { + "key": "Content-Length", + "value": "61" + } + ], + "cookie": [], + "body": "{\n \"id\": 9,\n \"message\": \"Successfully received and saved contract\"\n}" + } + ] } ] } \ No newline at end of file diff --git a/routes/public_routes.go b/routes/public_routes.go index 36eda70..fe367bc 100644 --- a/routes/public_routes.go +++ b/routes/public_routes.go @@ -48,4 +48,8 @@ func RegisterPublicRoutes(r *gin.Engine) { r.GET("/stats/contracts/total", controllers.GetTotalContractCount) r.GET("/stats/invoices", controllers.GetInvoiceCountByStatus) r.GET("/stats/milestones", controllers.GetContractsMatchingDeviceLocation) + + //Users + r.POST("/user/reset/password", controllers.ResetPassword) + r.POST("/user/set/password", controllers.UpdatePassword) } diff --git a/services/messaging/messaging_service.go b/services/messaging/messaging_service.go index 603dc17..792a05a 100644 --- a/services/messaging/messaging_service.go +++ b/services/messaging/messaging_service.go @@ -2,24 +2,48 @@ package messaging import ( "fmt" + "log" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ses" "github.com/jinzhu/gorm" + "gitlab.com/pactual1/backend/config" "gitlab.com/pactual1/backend/models" ) type service struct { - ch chan models.Notification - db *gorm.DB + ch chan models.Notification + db *gorm.DB + ech chan models.EmailNotification + ses *ses.SES } var MessagingChannel chan models.Notification +var EmailChannel chan models.EmailNotification -func NewService(ch chan models.Notification, db *gorm.DB) service { +func NewService(ch chan models.Notification, ech chan models.EmailNotification, db *gorm.DB) service { MessagingChannel = ch - return service{ - ch: MessagingChannel, - db: db, + EmailChannel = ech + log.Printf("Aws %v , %v", config.AppConfig.AWS.AccessKey, config.AppConfig.AWS.SecretKey) + // Create a new session in the us-west-2 region. + sess, err := session.NewSession(&aws.Config{ + Region: aws.String("us-east-1"), + Credentials: credentials.NewStaticCredentials(config.AppConfig.AWS.AccessKey, config.AppConfig.AWS.SecretKey, "")}, + ) + if err != nil { + fmt.Printf("Error creating AWS session: %v\n", err) + return service{} } + // Create an SES session. + svc := ses.New(sess) + return service{ + ch: MessagingChannel, + ech: EmailChannel, + ses: svc, + } + } func (s service) MessagingService() { @@ -31,8 +55,45 @@ func (s service) MessagingService() { fmt.Printf("Error saving notification to DB: %v\n", err) } } + +} + +func (s service) SendEmailService() { + for emailNotification := range s.ech { + fmt.Println("Email Service received: ", emailNotification) + + // Send email via SES + input := &ses.SendEmailInput{ + Destination: &ses.Destination{ + ToAddresses: []*string{ + aws.String(emailNotification.Email), + }, + }, + Message: &ses.Message{ + Body: &ses.Body{ + Text: &ses.Content{ + Data: aws.String(emailNotification.Body), + }, + }, + Subject: &ses.Content{ + Data: aws.String(emailNotification.Subject), + }, + }, + Source: aws.String("app@pactualdev.com"), // Replace with your SES verified email address + } + _, err := s.ses.SendEmail(input) + if err != nil { + fmt.Printf("Error sending email: %v\n", err) + } else { + fmt.Println("Email sent successfully") + } + } } func GetMessagingChannel() chan models.Notification { return MessagingChannel -} \ No newline at end of file +} + +func GetEmailChannel() chan models.EmailNotification { + return EmailChannel +} diff --git a/shared/database.go b/shared/database.go index c869b99..b1edab4 100644 --- a/shared/database.go +++ b/shared/database.go @@ -18,16 +18,14 @@ func Init() error { // port := config.AppConfig.Database.Port dbName := config.AppConfig.Database.DatabaseName password := config.AppConfig.Database.Password - dbString := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s", host, user, dbName, password) // db, err = gorm.Open("postgres", "host=localhost user=postgres dbname=postgres sslmode=disable password=root") var err error - + // //PostgreSQL db, err = gorm.Open("postgres", dbString) - db.LogMode(true) if err != nil { @@ -35,9 +33,9 @@ func Init() error { return err } //TODO AUTOMIGRATE models once we have them - db.AutoMigrate(&models.User{}, &models.Company{}, &models.Device{}, &models.DeviceInfo{}, + db.AutoMigrate(&models.User{}, &models.Company{}, &models.Device{}, &models.DeviceInfo{}, &models.Contract{}, &models.ContractInfo{}, - &models.ProductTemplate{}, &models.TextTemplate{}, &models.Invoice{}, &models.InvoiceItem{}, &models.Notification{} ) + &models.ProductTemplate{}, &models.TextTemplate{}, &models.Invoice{}, &models.InvoiceItem{}, &models.Notification{}, models.PasswordTokens{}) return nil