1
0
Fork 0
forked from goffee/cup

first commits 3

This commit is contained in:
Zeni Kim 2024-09-12 18:15:38 -05:00
parent b3fc6d25ca
commit 6dc72d3e14
28 changed files with 1095 additions and 0 deletions

22
LICENSE Normal file
View 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
View 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
View 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
View 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
View 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
View file

6
events/event-names.go Normal file
View 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"

View file

View 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()
}()
}

View 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()
}()
}

View 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()
}()
}

View 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
View 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
View 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
View 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
View 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
View 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())
}

View 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
View 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()
}

View 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
View 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
View 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)
}

View 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
View 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
View 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
View file

BIN
storage/sqlite.db Normal file

Binary file not shown.

19
utils/helpers.go Normal file
View 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
}