first commits 3
This commit is contained in:
parent
b3fc6d25ca
commit
6dc72d3e14
28 changed files with 1095 additions and 0 deletions
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 Harran Ali <harran.m@gmail.com>
|
||||
Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
21
config/cache.go
Normal file
21
config/cache.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import "git.smarteching.com/goffee/core"
|
||||
|
||||
// Retrieve the main config for the cache
|
||||
func GetCacheConfig() core.CacheConfig {
|
||||
//#####################################
|
||||
//# Main configuration for cache #####
|
||||
//#####################################
|
||||
|
||||
return core.CacheConfig{
|
||||
// For enabling and disabling the cache
|
||||
// set to true to enable it, set to false to disable
|
||||
EnableCache: true,
|
||||
}
|
||||
}
|
23
config/dotenvfile.go
Normal file
23
config/dotenvfile.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import "git.smarteching.com/goffee/core"
|
||||
|
||||
// Retrieve the main config for controlling the .env file
|
||||
func GetEnvFileConfig() core.EnvFileConfig {
|
||||
//#########################################################
|
||||
//# Main configuration for controlling the .env file #####
|
||||
//#########################################################
|
||||
|
||||
return core.EnvFileConfig{
|
||||
// Set to true to read the environment variables from the .env file and then
|
||||
// inject them into the os environment, please keep in mind this will override any
|
||||
// variables previously set in the os envrionment
|
||||
// set to false to ignore the .env file
|
||||
UseDotEnvFile: true,
|
||||
}
|
||||
}
|
21
config/gorm.go
Normal file
21
config/gorm.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import "git.smarteching.com/goffee/core"
|
||||
|
||||
// Retrieve the main config for the GORM
|
||||
func GetGormConfig() core.GormConfig {
|
||||
//#####################################
|
||||
//# Main configuration for GORM #####
|
||||
//#####################################
|
||||
|
||||
return core.GormConfig{
|
||||
// For enabling and disabling the GORM
|
||||
// set to true to enable it, set to false to disable
|
||||
EnableGorm: false,
|
||||
}
|
||||
}
|
20
config/request.go
Normal file
20
config/request.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import "git.smarteching.com/goffee/core"
|
||||
|
||||
// Retrieve the main config for the HTTP request
|
||||
func GetRequestConfig() core.RequestConfig {
|
||||
//#####################################
|
||||
//# Main configuration for gorm #####
|
||||
//#####################################
|
||||
|
||||
return core.RequestConfig{
|
||||
// Set the max file upload size
|
||||
MaxUploadFileSize: 40000000, // ~40MB
|
||||
}
|
||||
}
|
0
events/.gitkeep
Normal file
0
events/.gitkeep
Normal file
6
events/event-names.go
Normal file
6
events/event-names.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package events
|
||||
|
||||
// event names
|
||||
const USER_REGISTERED = "user-registered"
|
||||
const USER_PASSWORD_RESET_REQUESTED = "user-password-reset-requested"
|
||||
const PASSWORD_CHANGED = "password-changed"
|
0
events/eventjobs/.gitkeep
Normal file
0
events/eventjobs/.gitkeep
Normal file
31
events/eventjobs/send-password-changed-email.go
Normal file
31
events/eventjobs/send-password-changed-email.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package eventjobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/models"
|
||||
)
|
||||
|
||||
var SendPasswordChangedEmail core.EventJob = func(event *core.Event, c *core.Context) {
|
||||
go func() {
|
||||
mailer := c.GetMailer()
|
||||
logger := c.GetLogger()
|
||||
|
||||
user, ok := event.Payload["user"].(models.User)
|
||||
if !ok {
|
||||
logger.Error("[SendPasswordChangedEmail job] invalid user")
|
||||
return
|
||||
}
|
||||
mailer.SetFrom(core.EmailAddress{Name: "Goffee", Address: "mail@example.com"})
|
||||
mailer.SetTo([]core.EmailAddress{
|
||||
{
|
||||
Name: user.Name, Address: user.Email,
|
||||
},
|
||||
})
|
||||
mailer.SetSubject("Password Changed")
|
||||
body := fmt.Sprintf("Hi %v, \nYour password have been changed. \nThanks.", user.Name)
|
||||
mailer.SetPlainTextBody(body)
|
||||
mailer.Send()
|
||||
}()
|
||||
}
|
38
events/eventjobs/send-reset-password-email.go
Normal file
38
events/eventjobs/send-reset-password-email.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package eventjobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/models"
|
||||
)
|
||||
|
||||
var SendResetPasswordEmail core.EventJob = func(event *core.Event, c *core.Context) {
|
||||
go func() {
|
||||
mailer := c.GetMailer()
|
||||
logger := c.GetLogger()
|
||||
|
||||
user, ok := event.Payload["user"].(models.User)
|
||||
if !ok {
|
||||
logger.Error("[SendResetPasswordEmail job] invalid user")
|
||||
return
|
||||
}
|
||||
mailer.SetFrom(core.EmailAddress{Name: "Goffee", Address: "mail@example.com"})
|
||||
mailer.SetTo([]core.EmailAddress{
|
||||
{
|
||||
Name: user.Name, Address: user.Email,
|
||||
},
|
||||
})
|
||||
|
||||
mailer.SetSubject("Reset Password Link")
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
}
|
||||
resetPasswordLink := fmt.Sprintf("%v/reset-password/code/%v", hostname, c.CastToString(event.Payload["code"]))
|
||||
body := fmt.Sprintf("Hi %v, <br>Click the link below to reset your password <br><a href=\"%v\">Reset Password</a>. <br>Thanks.", user.Name, resetPasswordLink)
|
||||
mailer.SetHTMLBody(body)
|
||||
mailer.Send()
|
||||
}()
|
||||
}
|
31
events/eventjobs/send-welcome-email.go
Normal file
31
events/eventjobs/send-welcome-email.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package eventjobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/models"
|
||||
)
|
||||
|
||||
var SendWelcomeEmail core.EventJob = func(event *core.Event, c *core.Context) {
|
||||
go func() {
|
||||
mailer := c.GetMailer()
|
||||
logger := c.GetLogger()
|
||||
|
||||
user, ok := event.Payload["user"].(models.User)
|
||||
if !ok {
|
||||
logger.Error("[SenEmail job] invalid user")
|
||||
return
|
||||
}
|
||||
mailer.SetFrom(core.EmailAddress{Name: "Goffee", Address: "mail@example.com"})
|
||||
mailer.SetTo([]core.EmailAddress{
|
||||
{
|
||||
Name: user.Name, Address: user.Email,
|
||||
},
|
||||
})
|
||||
mailer.SetSubject("Welcome To Goffee")
|
||||
body := fmt.Sprintf("Hi %v, \nWelcome to Goffe \nYour account have been created successfully. \nThanks.", user.Name)
|
||||
mailer.SetPlainTextBody(body)
|
||||
mailer.Send()
|
||||
}()
|
||||
}
|
9
events/eventjobs/test-job.go
Normal file
9
events/eventjobs/test-job.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package eventjobs
|
||||
|
||||
import (
|
||||
"git.smarteching.com/goffee/core"
|
||||
)
|
||||
|
||||
var TestEvent core.EventJob = func(event *core.Event, c *core.Context) {
|
||||
c.GetLogger().Info("hello from event test job")
|
||||
}
|
52
go.mod
Normal file
52
go.mod
Normal file
|
@ -0,0 +1,52 @@
|
|||
module git.smarteching.com/goffee/cup
|
||||
|
||||
replace (
|
||||
git.smarteching.com/goffee/cup/config => ./config
|
||||
git.smarteching.com/goffee/cup/handlers => ./handlers
|
||||
git.smarteching.com/goffee/cup/middlewares => ./middlewares
|
||||
git.smarteching.com/goffee/cup/models => ./models
|
||||
)
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
git.smarteching.com/goffee/core v1.7.2
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/SparkPost/gosparkpost v0.2.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.11 // indirect
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/harranali/mailing v1.2.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailgun/mailgun-go/v4 v4.12.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.3.1 // indirect
|
||||
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gorm.io/driver/mysql v1.5.2 // indirect
|
||||
gorm.io/driver/postgres v1.5.4 // indirect
|
||||
gorm.io/driver/sqlite v1.5.4 // indirect
|
||||
)
|
119
go.sum
Normal file
119
go.sum
Normal file
|
@ -0,0 +1,119 @@
|
|||
github.com/SparkPost/gosparkpost v0.2.0 h1:yzhHQT7cE+rqzd5tANNC74j+2x3lrPznqPJrxC1yR8s=
|
||||
github.com/SparkPost/gosparkpost v0.2.0/go.mod h1:S9WKcGeou7cbPpx0kTIgo8Q69WZvUmVeVzbD+djalJ4=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/brianvoe/gofakeit/v6 v6.21.0 h1:tNkm9yxEbpuPK8Bx39tT4sSc5i9SUGiciLdNix+VDQY=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
|
||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gocondor/core v1.7.2 h1:fL3UoR1k0zfrPpiOTK0rY2r/IrrEQ7elMPv9XsNOz3s=
|
||||
github.com/gocondor/core v1.7.2/go.mod h1:BDg37tlLHYtjgW/5zgpRi/Ted8GLwE/jZesCRntbSiQ=
|
||||
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/harranali/mailing v1.2.0 h1:ihIyJwB8hyRVcdk+v465wk1PHMrSrgJqo/kMd+gZClY=
|
||||
github.com/harranali/mailing v1.2.0/go.mod h1:4a5N3yG98pZKluMpmcYlTtll7bisvOfGQEMIng3VQk4=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
|
||||
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jhillyerd/enmime v0.8.0/go.mod h1:MBHs3ugk03NGjMM6PuRynlKf+HA5eSillZ+TRCm73AE=
|
||||
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mailgun/mailgun-go/v4 v4.12.0 h1:TtuQCgqSp4cB6swPxP5VF/u4JeeBIAjTdpuQ+4Usd/w=
|
||||
github.com/mailgun/mailgun-go/v4 v4.12.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
|
||||
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA=
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
|
||||
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
391
handlers/authentication.go
Normal file
391
handlers/authentication.go
Normal file
|
@ -0,0 +1,391 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/events"
|
||||
"git.smarteching.com/goffee/cup/models"
|
||||
"git.smarteching.com/goffee/cup/utils"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Signup(c *core.Context) *core.Response {
|
||||
name := c.GetRequestParam("name")
|
||||
email := c.GetRequestParam("email")
|
||||
password := c.GetRequestParam("password")
|
||||
// check if email exists
|
||||
var user models.User
|
||||
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
|
||||
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
c.GetLogger().Error(res.Error.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal error",
|
||||
}))
|
||||
}
|
||||
if res.Error == nil {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "email already exists in the database",
|
||||
}))
|
||||
}
|
||||
|
||||
// validation data
|
||||
data := map[string]interface{}{
|
||||
"name": name,
|
||||
"email": email,
|
||||
"password": password,
|
||||
}
|
||||
// validation rules
|
||||
rules := map[string]interface{}{
|
||||
"name": "required|alphaNumeric",
|
||||
"email": "required|email",
|
||||
"password": "required|length:6,10",
|
||||
}
|
||||
// validate
|
||||
v := c.GetValidator().Validate(data, rules)
|
||||
if v.Failed() {
|
||||
c.GetLogger().Error(v.GetErrorMessagesJson())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
|
||||
}
|
||||
|
||||
//hash the password
|
||||
passwordHashed, err := c.GetHashing().HashPassword(c.CastToString(password))
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": err.Error(),
|
||||
}))
|
||||
}
|
||||
// store the record in db
|
||||
user = models.User{
|
||||
Name: c.CastToString(name),
|
||||
Email: c.CastToString(email),
|
||||
Password: passwordHashed,
|
||||
}
|
||||
res = c.GetGorm().Create(&user)
|
||||
if res.Error != nil {
|
||||
c.GetLogger().Error(res.Error.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": res.Error.Error(),
|
||||
}))
|
||||
}
|
||||
|
||||
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
|
||||
"userID": user.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
// cache the token
|
||||
userAgent := c.GetUserAgent()
|
||||
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
|
||||
err = c.GetCache().Set(hashedCacheKey, token)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
// fire user registered event
|
||||
err = c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{
|
||||
"user": user,
|
||||
}})
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
"token": token,
|
||||
}))
|
||||
}
|
||||
|
||||
func Signin(c *core.Context) *core.Response {
|
||||
email := c.GetRequestParam("email")
|
||||
password := c.GetRequestParam("password")
|
||||
|
||||
data := map[string]interface{}{
|
||||
"email": email,
|
||||
"password": password,
|
||||
}
|
||||
rules := map[string]interface{}{
|
||||
"email": "required|email",
|
||||
"password": "required",
|
||||
}
|
||||
v := c.GetValidator().Validate(data, rules)
|
||||
|
||||
if v.Failed() {
|
||||
c.GetLogger().Error(v.GetErrorMessagesJson())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
|
||||
}
|
||||
|
||||
var user models.User
|
||||
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
|
||||
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
c.GetLogger().Error(res.Error.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid email or password",
|
||||
}))
|
||||
}
|
||||
|
||||
ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password))
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": err.Error(),
|
||||
}))
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid email or password",
|
||||
}))
|
||||
}
|
||||
|
||||
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
|
||||
"userID": user.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
// cache the token
|
||||
userAgent := c.GetUserAgent()
|
||||
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
|
||||
err = c.GetCache().Set(hashedCacheKey, token)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
"token": token,
|
||||
}))
|
||||
}
|
||||
|
||||
func ResetPasswordRequest(c *core.Context) *core.Response {
|
||||
email := c.GetRequestParam("email")
|
||||
|
||||
// validation data
|
||||
data := map[string]interface{}{
|
||||
"email": email,
|
||||
}
|
||||
// validation rules
|
||||
rules := map[string]interface{}{
|
||||
"email": "required|email",
|
||||
}
|
||||
// validate
|
||||
v := c.GetValidator().Validate(data, rules)
|
||||
if v.Failed() {
|
||||
c.GetLogger().Error(v.GetErrorMessagesJson())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
|
||||
}
|
||||
|
||||
// check email in the database
|
||||
var user models.User
|
||||
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
|
||||
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
c.GetLogger().Error(res.Error.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "email not found in our database",
|
||||
}))
|
||||
}
|
||||
|
||||
// generate the link
|
||||
expiresAt := time.Now().Add(time.Hour * 3).Unix()
|
||||
linkCodeData := map[string]string{
|
||||
"userID": c.CastToString(user.ID),
|
||||
"expiresAt": c.CastToString(expiresAt),
|
||||
}
|
||||
code := uuid.NewString()
|
||||
c.GetCache().SetWithExpiration(code, c.MapToJson(linkCodeData), time.Hour*3)
|
||||
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_PASSWORD_RESET_REQUESTED, Payload: map[string]interface{}{
|
||||
"user": user,
|
||||
"code": code,
|
||||
}})
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
"message": "reset password email sent successfully",
|
||||
}))
|
||||
}
|
||||
|
||||
func SetNewPassword(c *core.Context) *core.Response {
|
||||
urlCode := c.CastToString(c.GetPathParam("code"))
|
||||
linkCodeDataStr, err := c.GetCache().Get(urlCode)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid link",
|
||||
}))
|
||||
}
|
||||
var linkCode map[string]interface{}
|
||||
json.Unmarshal([]byte(linkCodeDataStr), &linkCode)
|
||||
expiresAtUnix, err := strconv.ParseInt(c.CastToString(linkCode["expiresAt"]), 10, 64)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid link",
|
||||
}))
|
||||
}
|
||||
expiresAt := time.Unix(expiresAtUnix, 0)
|
||||
if time.Now().After(expiresAt) {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid link",
|
||||
}))
|
||||
}
|
||||
userID, err := strconv.ParseUint(c.CastToString(linkCode["userID"]), 10, 64)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid link",
|
||||
}))
|
||||
}
|
||||
|
||||
oldPassword := c.CastToString(c.GetRequestParam("old_password"))
|
||||
newPassword := c.CastToString(c.GetRequestParam("new_password"))
|
||||
newPasswordConfirm := c.CastToString(c.GetRequestParam("new_password_confirm"))
|
||||
|
||||
// validation data
|
||||
data := map[string]interface{}{
|
||||
"old_password": oldPassword,
|
||||
"new_password": newPassword,
|
||||
"new_password_confirm": newPasswordConfirm,
|
||||
}
|
||||
// validation rules
|
||||
rules := map[string]interface{}{
|
||||
"old_password": "required|length:6,10",
|
||||
"new_password": "required|length:6,10",
|
||||
"new_password_confirm": "required|length:6,10",
|
||||
}
|
||||
// validate
|
||||
v := c.GetValidator().Validate(data, rules)
|
||||
if v.Failed() {
|
||||
c.GetLogger().Error(v.GetErrorMessagesJson())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
|
||||
}
|
||||
|
||||
var user models.User
|
||||
res := c.GetGorm().Where("id = ?", userID).First(&user)
|
||||
if res.Error != nil {
|
||||
c.GetLogger().Error(res.Error.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid link",
|
||||
}))
|
||||
}
|
||||
|
||||
SamePassword, err := c.GetHashing().CheckPasswordHash(user.Password, oldPassword)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid link",
|
||||
}))
|
||||
}
|
||||
|
||||
if !SamePassword {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "the old password is incorrect",
|
||||
}))
|
||||
}
|
||||
|
||||
if newPassword != newPasswordConfirm {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "new password does not match new password confirmation",
|
||||
}))
|
||||
}
|
||||
|
||||
hashedNewPassword, err := c.GetHashing().HashPassword(newPassword)
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid password",
|
||||
}))
|
||||
}
|
||||
|
||||
user.Password = hashedNewPassword
|
||||
c.GetGorm().Save(&user)
|
||||
|
||||
err = c.GetEventsManager().Fire(&core.Event{Name: events.PASSWORD_CHANGED, Payload: map[string]interface{}{
|
||||
"user": user,
|
||||
}})
|
||||
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
"message": "password changed successfully",
|
||||
}))
|
||||
}
|
||||
|
||||
func Signout(c *core.Context) *core.Response {
|
||||
tokenRaw := c.GetHeader("Authorization")
|
||||
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
|
||||
if token == "" {
|
||||
return c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
}))
|
||||
}
|
||||
payload, err := c.GetJWT().DecodeToken(token)
|
||||
if err != nil {
|
||||
return c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
}))
|
||||
}
|
||||
userAgent := c.GetUserAgent()
|
||||
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
||||
|
||||
err = c.GetCache().Delete(hashedCacheKey)
|
||||
if err != nil {
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "internal error",
|
||||
}))
|
||||
}
|
||||
|
||||
return c.Response.SetStatusCode(http.StatusOK).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "signed out successfully",
|
||||
}))
|
||||
}
|
22
handlers/home.go
Normal file
22
handlers/home.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"git.smarteching.com/goffee/core"
|
||||
)
|
||||
|
||||
// Show home page
|
||||
func WelcomeHome(c *core.Context) *core.Response {
|
||||
message := "{\"message\": \"Welcome to Goffee\"}"
|
||||
return c.Response.Json(message)
|
||||
}
|
||||
|
||||
// Show dashboard
|
||||
func WelcomeToDashboard(c *core.Context) *core.Response {
|
||||
message := "{\"message\": \"Welcome to Dashboard\"}"
|
||||
return c.Response.Json(message)
|
||||
}
|
53
main.go
Normal file
53
main.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/core/env"
|
||||
"git.smarteching.com/goffee/core/logger"
|
||||
"git.smarteching.com/goffee/cup/config"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// The main function
|
||||
func main() {
|
||||
app := core.New()
|
||||
basePath, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("error getting current working dir")
|
||||
}
|
||||
app.SetBasePath(basePath)
|
||||
app.MakeDirs("logs", "storage", "storage/sqlite", "tls")
|
||||
// Handle the reading of the .env file
|
||||
if config.GetEnvFileConfig().UseDotEnvFile {
|
||||
envVars, err := godotenv.Read(".env")
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
env.SetEnvVars(envVars)
|
||||
}
|
||||
// Handle the logs
|
||||
app.SetLogsDriver(&logger.LogFileDriver{
|
||||
FilePath: path.Join(basePath, "logs/app.log"),
|
||||
})
|
||||
app.SetRequestConfig(config.GetRequestConfig())
|
||||
app.SetGormConfig(config.GetGormConfig())
|
||||
app.SetCacheConfig(config.GetCacheConfig())
|
||||
app.Bootstrap()
|
||||
registerGlobalMiddlewares()
|
||||
registerRoutes()
|
||||
registerEvents()
|
||||
if config.GetGormConfig().EnableGorm == true {
|
||||
RunAutoMigrations()
|
||||
}
|
||||
app.Run(httprouter.New())
|
||||
}
|
18
middlewares/another-example-middleware.go.go
Normal file
18
middlewares/another-example-middleware.go.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
)
|
||||
|
||||
// Another example middleware
|
||||
var AnotherExampleMiddleware core.Middleware = func(c *core.Context) {
|
||||
fmt.Println("another example middleware!")
|
||||
c.Next()
|
||||
}
|
69
middlewares/auth-check.go
Normal file
69
middlewares/auth-check.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/models"
|
||||
"git.smarteching.com/goffee/cup/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var AuthCheck core.Middleware = func(c *core.Context) {
|
||||
tokenRaw := c.GetHeader("Authorization")
|
||||
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
|
||||
if token == "" {
|
||||
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
})).ForceSendResponse()
|
||||
return
|
||||
}
|
||||
payload, err := c.GetJWT().DecodeToken(token)
|
||||
if err != nil {
|
||||
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
})).ForceSendResponse()
|
||||
return
|
||||
}
|
||||
userAgent := c.GetUserAgent()
|
||||
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
|
||||
|
||||
cachedToken, err := c.GetCache().Get(hashedCacheKey)
|
||||
if err != nil {
|
||||
// user signed out
|
||||
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
})).ForceSendResponse()
|
||||
return
|
||||
}
|
||||
if cachedToken != token {
|
||||
// using old token replaced with new one after recent signin
|
||||
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
})).ForceSendResponse()
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
res := c.GetGorm().Where("id = ?", payload["userID"]).First(&user)
|
||||
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
// error with the database
|
||||
c.GetLogger().Error(res.Error.Error())
|
||||
c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "internal error",
|
||||
})).ForceSendResponse()
|
||||
return
|
||||
}
|
||||
|
||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
// user record is not found (deleted)
|
||||
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "unauthorized",
|
||||
})).ForceSendResponse()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
18
middlewares/example-middleware.go
Normal file
18
middlewares/example-middleware.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.smarteching.com/goffee/core"
|
||||
)
|
||||
|
||||
// Example middleware
|
||||
var ExampleMiddleware core.Middleware = func(c *core.Context) {
|
||||
fmt.Println("example middleware!")
|
||||
c.Next()
|
||||
}
|
20
models/user.go
Normal file
20
models/user.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Override the table name
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
26
register-events.go
Normal file
26
register-events.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/events"
|
||||
"git.smarteching.com/goffee/cup/events/eventjobs"
|
||||
)
|
||||
|
||||
// Register events
|
||||
func registerEvents() {
|
||||
eventsManager := core.ResolveEventsManager()
|
||||
//########################################
|
||||
//# events registration #####
|
||||
//########################################
|
||||
|
||||
// register your event here ...
|
||||
eventsManager.Register(events.USER_REGISTERED, eventjobs.SendWelcomeEmail)
|
||||
eventsManager.Register(events.USER_REGISTERED, eventjobs.TestEvent)
|
||||
eventsManager.Register(events.USER_PASSWORD_RESET_REQUESTED, eventjobs.SendResetPasswordEmail)
|
||||
eventsManager.Register(events.PASSWORD_CHANGED, eventjobs.SendPasswordChangedEmail)
|
||||
}
|
16
register-global-middlewares.go
Normal file
16
register-global-middlewares.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
// Register middlewares globally
|
||||
func registerGlobalMiddlewares() {
|
||||
//########################################
|
||||
//# Global middlewares registration #####
|
||||
//########################################
|
||||
|
||||
// Register global middlewares here ...
|
||||
// core.UseMiddleware(middlewares.AnotherExampleMiddleware)
|
||||
}
|
29
routes.go
Normal file
29
routes.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/handlers"
|
||||
)
|
||||
|
||||
// Register the app routes
|
||||
func registerRoutes() {
|
||||
router := core.ResolveRouter()
|
||||
//#############################
|
||||
//# App Routes #####
|
||||
//#############################
|
||||
|
||||
// Define your routes here...
|
||||
router.Get("/", handlers.WelcomeHome)
|
||||
// Uncomment the lines below to enable authentication
|
||||
// router.Post("/signup", handlers.Signup)
|
||||
// router.Post("/signin", handlers.Signin)
|
||||
// router.Post("/signout", handlers.Signout)
|
||||
// router.Post("/reset-password", handlers.ResetPasswordRequest)
|
||||
// router.Post("/reset-password/code/:code", handlers.SetNewPassword)
|
||||
// router.Get("/dashboard", handlers.WelcomeToDashboard, middlewares.AuthCheck)
|
||||
}
|
21
run-auto-migrations.go
Normal file
21
run-auto-migrations.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.smarteching.com/goffee/core"
|
||||
"git.smarteching.com/goffee/cup/models"
|
||||
)
|
||||
|
||||
func RunAutoMigrations() {
|
||||
db := core.ResolveGorm()
|
||||
//##############################
|
||||
//# Models auto migration #####
|
||||
//##############################
|
||||
|
||||
// Add auto migrations for your models here...
|
||||
db.AutoMigrate(&models.User{})
|
||||
}
|
0
storage/.gitignore
vendored
Normal file
0
storage/.gitignore
vendored
Normal file
BIN
storage/sqlite.db
Normal file
BIN
storage/sqlite.db
Normal file
Binary file not shown.
19
utils/helpers.go
Normal file
19
utils/helpers.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
|
||||
// Copyright (c) 2024 Zeni Kim <zenik@smarteching.com>
|
||||
// Use of this source code is governed by MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// generate a hashed string to be used as key for caching auth jwt token
|
||||
func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string {
|
||||
cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent)
|
||||
hashedCacheKey := fmt.Sprintf("%v", fmt.Sprintf("%x", md5.Sum([]byte(cacheKey))))
|
||||
|
||||
return hashedCacheKey
|
||||
}
|
Loading…
Reference in a new issue