diff --git a/go.mod b/go.mod index 4d4b5718..1105efce 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.9.0 github.com/go-redis/redis/v8 v8.11.4 github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.1.2 github.com/labstack/echo-contrib v0.11.0 github.com/labstack/echo/v4 v4.6.3 github.com/labstack/gommon v0.3.1 @@ -15,6 +16,7 @@ require ( github.com/spf13/cobra v1.3.0 github.com/spf13/viper v1.10.0 github.com/stretchr/testify v1.7.0 + github.com/valyala/fasttemplate v1.2.1 go.mongodb.org/mongo-driver v1.8.2 gorm.io/datatypes v1.0.5 gorm.io/driver/mysql v1.2.3 @@ -63,7 +65,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect diff --git a/go.sum b/go.sum index 38aad69b..087b96c0 100644 --- a/go.sum +++ b/go.sum @@ -316,6 +316,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= diff --git a/internal/project/commands/start.go b/internal/project/commands/start.go index 81f08bb6..aad7ef72 100644 --- a/internal/project/commands/start.go +++ b/internal/project/commands/start.go @@ -6,9 +6,6 @@ import ( "demo/framework/bean" /*#bean.replace("{{ .PkgPath }}/framework/bean")**/ /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ - /**#bean*/ beanValidator "demo/framework/internals/validator" /*#bean.replace(beanValidator "{{ .PkgPath }}/framework/internals/validator")**/ /**#bean*/ @@ -55,17 +52,16 @@ func init() { func start(cmd *cobra.Command, args []string) { // Create a bean object b := bean.New() - global.EchoInstance = b.Echo - global.DBConn = (*global.DBDeps)(b.DBConn) - - b.InitDB() b.BeforeServe = func() { - // init global middleware if you need + // Init global middleware if you need // middlerwares.Init() - // init router - routers.Init() + // Init DB dependency. + b.InitDB() + + // Init different routes. + routers.Init(b) } // Below is an example of how you can initialize your own validator. Just create a new directory diff --git a/internal/project/env.json b/internal/project/env.json index 014a8ce2..bf94e546 100644 --- a/internal/project/env.json +++ b/internal/project/env.json @@ -2,11 +2,10 @@ "beanVersion": "{{ .BeanVersion }}", "packagePath": "{{ .PkgPath }}", "projectName": "{{ .PkgName }}", - "projectVersion": "1.0", "environment": "local", - "isLogStdout": false, - "logFile": "logs/console.log", - "isBodyDump": true, + "debugLog": "", + "requestLog": "", + "bodydumpLog": "", "prometheus": { "isPrometheusMetrics": false, "skipEndpoints": ["/ping", "/route/stats"] diff --git a/internal/project/framework/bean/bean.go b/internal/project/framework/bean/bean.go index cb1a2b69..81253a4f 100644 --- a/internal/project/framework/bean/bean.go +++ b/internal/project/framework/bean/bean.go @@ -130,7 +130,6 @@ func (b *Bean) InitDB() { isTenant := viper.GetBool("database.mysql.isTenant") if isTenant { - dbdrivers.InitTenantIdMutexMap() masterMySQLDB, masterMySQLDBName = dbdrivers.InitMysqlMasterConn() tenantMySQLDBs, tenantMySQLDBNames = dbdrivers.InitMysqlTenantConns(masterMySQLDB) tenantMongoDBs, tenantMongoDBNames = dbdrivers.InitMongoTenantConns(masterMySQLDB) diff --git a/internal/project/framework/internals/async/async.go b/internal/project/framework/internals/async/async.go index 4f5400f5..451e4690 100644 --- a/internal/project/framework/internals/async/async.go +++ b/internal/project/framework/internals/async/async.go @@ -3,30 +3,25 @@ package async import ( - "fmt" - "reflect" - /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ + "demo/framework/internals/sentry" + /*#bean.replace("{{ .PkgPath }}/framework/internals/sentry")**/ - "github.com/getsentry/sentry-go" - sentryecho "github.com/getsentry/sentry-go/echo" "github.com/labstack/echo/v4" - "github.com/spf13/viper" ) -// `Execute` provides a safe way to execute a function asynchronously, recovering if they panic +type Task func(c echo.Context) + +// `Execute` provides a safe way to execute a function asynchronously, recovering if they panic // and provides all error stack aiming to facilitate fail causes discovery. -func Execute(fn func(ctx echo.Context)) { +func Execute(fn Task, e *echo.Echo) { go func() { // Acquire a context from global echo instance and reset it to avoid race condition. - c := global.EchoInstance.AcquireContext() + c := e.AcquireContext() c.Reset(nil, nil) defer recoverPanic(c) - fn(c) }() } @@ -35,49 +30,9 @@ func Execute(fn func(ctx echo.Context)) { func recoverPanic(c echo.Context) { if r := recover(); r != nil { - err, ok := r.(error) - if !ok { - err = fmt.Errorf("%v", r) - } - - // Run this function synchronously to release the `context` properly. - sendErrorToSentry(c, err) + sentry.PushData(c, r, nil, false) } // Release the acquired context. - global.EchoInstance.ReleaseContext(c) -} - -// This function is only use in this package/file to avoid import cycle. -// For normal sentry usage, please refer to the `{{ .PkgPath }}/internals/sentry` package. -func sendErrorToSentry(c echo.Context, err error) { - - isSentry := viper.GetBool("sentry.isSentry") - sentryDSN := viper.GetString("sentry.dsn") - - if !isSentry || sentryDSN == "" { - global.EchoInstance.Logger.Error(err) - return - } - - // IMPORTANT: Clone the current sentry hub from the echo context before it's gone. - hub := sentryecho.GetHubFromContext(c) - - if hub == nil { - hub = sentry.CurrentHub().Clone() - } - - client, scope := hub.Client(), hub.Scope() - - event := sentry.NewEvent() - event.Level = sentry.LevelError - event.Exception = []sentry.Exception{ - { - Value: err.Error(), - Type: reflect.TypeOf(err).String(), - Stacktrace: sentry.NewStacktrace(), - }, - } - - client.CaptureEvent(event, &sentry.EventHint{RecoveredException: err}, scope) + c.Echo().ReleaseContext(c) } diff --git a/internal/project/framework/internals/cli/cli.go b/internal/project/framework/internals/cli/cli.go deleted file mode 100644 index e97f1bcb..00000000 --- a/internal/project/framework/internals/cli/cli.go +++ /dev/null @@ -1,82 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package cli - -import ( - "fmt" - "os" - - /**#bean*/ - "demo/framework/internals/chalk" - /*#bean.replace("{{ .PkgPath }}/framework/internals/chalk")**/ - /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ - /**#bean*/ - "demo/framework/internals/sentry" - /*#bean.replace("{{ .PkgPath }}/framework/internals/sentry")**/ - - "github.com/labstack/echo/v4" -) - -// Write the error to console or sentry when a `gopher` command panics. -func RecoverPanic(c echo.Context) { - - if r := recover(); r != nil { - - err, ok := r.(error) - if !ok { - err = fmt.Errorf("%v", r) - } - - // Run this function synchronously to release the `context` properly. - sentry.PushData(c, err, nil, false) - } - - // Release the acquired context. `c` is a global context here. - // A single `gopher` command is using only one context in it's life time. - global.EchoInstance.ReleaseContext(c) - - // Exit the `gopher` command. - os.Exit(0) -} - -func DisplayHelpMessage(commandDescription map[string]string) { - - helpMessage := `{{ .PkgName }} 1.0 - -` + chalk.Bold.TextStyle("NAME:") + ` - {{ .PkgName }} gopher - This is a CLI and make utility for {{ .PkgName }} to do awesome stuff - -` + chalk.Bold.TextStyle("USAGE:") + ` - {{ .PkgName }} gopher command[:options] [arguments...] - or - make -- command[:options] [arguments...] - -` + chalk.Bold.TextStyle("NOTES:") + ` - ` + chalk.Bold.TextStyle("make") + ` supporting all ` + chalk.Bold.TextStyle("{{ .PkgName }} gopher") + ` command. Additionally, it's also supporting following: - start, stop, reload, restart, build, clean, install, run, get, all, test, debug commands. - -` + chalk.Bold.TextStyle("COMMANDS:") + ` - help Shows a list of commands or help for one command - route:list List all registered routes - -` + chalk.Bold.TextStyle("ADDITIONAL MAKE COMMANDS:") + ` - all Run ` + chalk.Bold.TextStyle("build") + ` and ` + chalk.Bold.TextStyle("test") + ` command - build Compile and install with race detection and place the file in /bin folder - clean Removes object files from package source directories and /bin folder - debug Start delve debug server (only for local env) - get Get and adds dependencies to the current development module and then builds and installs them - install Compile and install without race detection and place the file in /bin folder - lint Code linters aggregator - run Run the server in terminal - restart Restart the - reload Reload the config file - start Start the server - stop Stop the server gracefully -` - for k, v := range commandDescription { - helpMessage = helpMessage + k + `\t\t\t` + v + `\n` - } - - fmt.Print(helpMessage) -} diff --git a/internal/project/framework/internals/global/constants.go b/internal/project/framework/internals/global/constants.go deleted file mode 100644 index e2504821..00000000 --- a/internal/project/framework/internals/global/constants.go +++ /dev/null @@ -1,34 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package global - -import ( - "github.com/dgraph-io/badger/v3" - "github.com/go-redis/redis/v8" - "github.com/labstack/echo/v4" - "go.mongodb.org/mongo-driver/mongo" - "gorm.io/gorm" -) - -// All database connections are initialized using `DBDeps` structure. -type DBDeps struct { - MasterMySQLDB *gorm.DB - MasterMySQLDBName string - TenantMySQLDBs map[uint64]*gorm.DB - TenantMySQLDBNames map[uint64]string - MasterMongoDB *mongo.Client - MasterMongoDBName string - TenantMongoDBs map[uint64]*mongo.Client - TenantMongoDBNames map[uint64]string - MasterRedisDB *redis.Client - MasterRedisDBName int - TenantRedisDBs map[uint64]*redis.Client - TenantRedisDBNames map[uint64]int - BadgerDB *badger.DB -} - -// We sometimes need echo instance to print log from internal utils packages without passing echo context or instance -var ( - EchoInstance *echo.Echo - Environment string - DBConn *DBDeps // DBConn will be used by any repository function to connect right database. -) diff --git a/internal/project/framework/internals/helpers/common.go b/internal/project/framework/internals/helpers/common.go index fe96f71e..5727b45d 100644 --- a/internal/project/framework/internals/helpers/common.go +++ b/internal/project/framework/internals/helpers/common.go @@ -2,151 +2,18 @@ package helpers import ( - "fmt" "io/ioutil" "math/rand" - "os" "time" - /**#bean*/ - "demo/framework/dbdrivers" - /*#bean.replace("{{ .PkgPath }}/framework/dbdrivers")**/ - /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ /**#bean*/ str "demo/framework/internals/string" /*#bean.replace(str "{{ .PkgPath }}/framework/internals/string")**/ - - "github.com/getsentry/sentry-go" - "github.com/go-redis/redis/v8" - "github.com/labstack/echo/v4" - echomiddleware "github.com/labstack/echo/v4/middleware" - "github.com/labstack/gommon/log" - "github.com/spf13/viper" - "go.mongodb.org/mongo-driver/mongo" - "gorm.io/gorm" ) type CopyableMap map[string]interface{} type CopyableSlice []interface{} -// This function mainly used by various commands. So `panicking` here is absolutely OK. -func GetContextInstanceEnvironmentAndConfig() (*echo.Echo, echo.Context, string) { - - // Set viper path and read configuration. You must keep `env.json` file in the root of your project. - viper.AddConfigPath(".") - viper.SetConfigType("json") - viper.SetConfigName("env") - - err := viper.ReadInConfig() - if err != nil { - panic(err) - } - - e := echo.New() - c := e.AcquireContext() - c.Reset(nil, nil) - - // Get log type (file or stdout) settings from config. - isLogStdout := viper.GetBool("isLogStdout") - - e.Logger.SetLevel(log.DEBUG) - - if !isLogStdout { - - logFile := viper.GetString("logFile") - - // XXX: IMPORTANT - Set log output into file instead `stdout`. - logfp, err := os.OpenFile(logFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0664) - if err != nil { - fmt.Printf("Unable to open log file: %v Exiting...\n", err) - os.Exit(1) - } - - e.Logger.SetOutput(logfp) - - } else { - logger := echomiddleware.LoggerWithConfig(echomiddleware.LoggerConfig{ - Format: JsonLogFormat(), // we need additional access log parameter - }) - e.Use(logger) - } - - // Sentry `panic`` error handler initialization if activated from `env.json` - isSentry := viper.GetBool("sentry.isSentry") - if isSentry { - - // HTTPSyncTransport is an implementation of `Transport` interface which blocks after each captured event. - sentrySyncTransport := sentry.NewHTTPSyncTransport() - sentrySyncTransport.Timeout = time.Second * 30 // Blocks for maximum 30 seconds. - - // To initialize Sentry's handler, we need to initialize Sentry itself beforehand - err := sentry.Init(sentry.ClientOptions{ - Dsn: viper.GetString("sentry.dsn"), - AttachStacktrace: true, - Transport: sentrySyncTransport, - }) - if err != nil { - panic(err) - } - - defer sentry.Flush(2 * time.Second) - } - - // Initialize the global echo instance. This is useful to print log from internal utils packages. - global.EchoInstance = e - global.Environment = viper.GetString("environment") - - // Initialize all database driver. - var masterMySQLDB *gorm.DB - var masterMySQLDBName string - var masterMongoDB *mongo.Client - var masterMongoDBName string - var masterRedisDB *redis.Client - var masterRedisDBName int - - var tenantMySQLDBs map[uint64]*gorm.DB - var tenantMySQLDBNames map[uint64]string - var tenantMongoDBs map[uint64]*mongo.Client - var tenantMongoDBNames map[uint64]string - var tenantRedisDBs map[uint64]*redis.Client - var tenantRedisDBNames map[uint64]int - - isTenant := viper.GetBool("database.mysql.isTenant") - if isTenant { - masterMySQLDB, masterMySQLDBName = dbdrivers.InitMysqlMasterConn() - tenantMySQLDBs, tenantMySQLDBNames = dbdrivers.InitMysqlTenantConns(masterMySQLDB) - tenantMongoDBs, tenantMongoDBNames = dbdrivers.InitMongoTenantConns(masterMySQLDB) - tenantRedisDBs, tenantRedisDBNames = dbdrivers.InitRedisTenantConns(masterMySQLDB) - - } else { - masterMySQLDB, masterMySQLDBName = dbdrivers.InitMysqlMasterConn() - masterMongoDB, masterMongoDBName = dbdrivers.InitMongoMasterConn() - masterRedisDB, masterRedisDBName = dbdrivers.InitRedisMasterConn() - } - - masterBadgerDB := dbdrivers.InitBadgerConn() - - global.DBConn = &global.DBDeps{ - MasterMySQLDB: masterMySQLDB, - MasterMySQLDBName: masterMySQLDBName, - TenantMySQLDBs: tenantMySQLDBs, - TenantMySQLDBNames: tenantMySQLDBNames, - MasterMongoDB: masterMongoDB, - MasterMongoDBName: masterMongoDBName, - TenantMongoDBs: tenantMongoDBs, - TenantMongoDBNames: tenantMongoDBNames, - MasterRedisDB: masterRedisDB, - MasterRedisDBName: masterRedisDBName, - TenantRedisDBs: tenantRedisDBs, - TenantRedisDBNames: tenantRedisDBNames, - BadgerDB: masterBadgerDB, - } - - return e, c, global.Environment -} - func GetRandomNumberFromRange(min, max int) int { rand.Seed(time.Now().UnixNano()) @@ -156,15 +23,12 @@ func GetRandomNumberFromRange(min, max int) int { return n } -/* DeepCopy will create a deep copy of this map. The depth of this - * copy is all inclusive. Both maps and slices will be considered when - * making the copy. - * - * Keep in mind that the slices in the resulting map will be of type []interface{}, - * so when using them, you will need to use type assertion to retrieve the value in the expected type. - * - * Reference: https://stackoverflow.com/questions/23057785/how-to-copy-a-map/23058707 - */ +// DeepCopy will create a deep copy of this map. The depth of this +// copy is all inclusive. Both maps and slices will be considered when +// making the copy. +// Keep in mind that the slices in the resulting map will be of type []interface{}, +// so when using them, you will need to use type assertion to retrieve the value in the expected type. +// Reference: https://stackoverflow.com/questions/23057785/how-to-copy-a-map/23058707 func (m CopyableMap) DeepCopy() map[string]interface{} { result := map[string]interface{}{} @@ -190,12 +54,10 @@ func (m CopyableMap) DeepCopy() map[string]interface{} { return result } -/* DeepCopy will create a deep copy of this slice. The depth of this - * copy is all inclusive. Both maps and slices will be considered when - * making the copy. - * - * Reference: https://stackoverflow.com/questions/23057785/how-to-copy-a-map/23058707 - */ +// DeepCopy will create a deep copy of this slice. The depth of this +// copy is all inclusive. Both maps and slices will be considered when +// making the copy. +// Reference: https://stackoverflow.com/questions/23057785/how-to-copy-a-map/23058707 func (s CopyableSlice) DeepCopy() []interface{} { result := []interface{}{} diff --git a/internal/project/framework/internals/helpers/log.go b/internal/project/framework/internals/helpers/log.go deleted file mode 100644 index 91a3501b..00000000 --- a/internal/project/framework/internals/helpers/log.go +++ /dev/null @@ -1,37 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package helpers - -import ( - "github.com/labstack/echo/v4" -) - -func JsonLogFormat() string { - - var logFormat string - - logFormat += `{` - logFormat += `"time": "${time_rfc3339_nano}", ` - logFormat += `"level": "ACCESS", ` - logFormat += `"id": "${id}", ` - logFormat += `"remote_ip": "${remote_ip}", ` - logFormat += `"x-forwarded-for": "${header:x-forwarded-for}", ` - logFormat += `"host": "${host}", ` - logFormat += `"method": "${method}", ` - logFormat += `"uri": "${uri}", ` - logFormat += `"user_agent": "${user_agent}", ` - logFormat += `"status": ${status}, ` - logFormat += `"error": "${error}", ` - logFormat += `"latency": ${latency}, ` - logFormat += `"latency_human": "${latency_human}", ` - logFormat += `"bytes_in": ${bytes_in}, ` - logFormat += `"bytes_out": ${bytes_out}` - logFormat += "}\n" - - return logFormat -} - -func BodyDumpHandler(c echo.Context, reqBody, resBody []byte) { - - c.Logger().Info("Request Path: ", c.Path(), " | Request Body: ", string(reqBody)) - c.Logger().Info("Response Body: ", string(resBody)) -} diff --git a/internal/project/framework/internals/latency/latency.go b/internal/project/framework/internals/latency/latency.go deleted file mode 100644 index 34fc0799..00000000 --- a/internal/project/framework/internals/latency/latency.go +++ /dev/null @@ -1,94 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package latency - -import ( - "bytes" - "encoding/binary" - "time" - - /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ - - "github.com/dgraph-io/badger/v3" - "github.com/labstack/echo/v4" -) - -// Entry is a simple object storing latency -// and timestamp for an api response. -type Entry struct { - Latency time.Duration - Timestamp int64 -} - -// SetAPILatencyWithTTL will serialize the val into []byte and save it until TTL. -func SetAPILatencyWithTTL(c echo.Context, key string, val interface{}, ttl time.Duration) { - - db := global.DBConn.BadgerDB - - buf := new(bytes.Buffer) - err := binary.Write(buf, binary.BigEndian, val) - if err != nil { - panic(err) - } - - err = db.Update(func(txn *badger.Txn) error { - e := badger.NewEntry([]byte(key), buf.Bytes()).WithTTL(ttl) - err := txn.SetEntry(e) - - return err - }) - - if err != nil { - panic(err) - } -} - -// GetAllAPILatency returns a map containing all the api request latency data. -func GetAllAPILatency(c echo.Context) map[string]Entry { - - db := global.DBConn.BadgerDB - - latencyEntries := make(map[string]Entry) - - err := db.View(func(txn *badger.Txn) error { - - it := txn.NewIterator(badger.DefaultIteratorOptions) - defer it.Close() - - // All api uri start with "/". - prefix := []byte("/") - for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { - - item := it.Item() - k := item.Key() - - err := item.Value(func(v []byte) error { - - var e Entry - buf := bytes.NewBuffer(v) - - err := binary.Read(buf, binary.BigEndian, &e) - if err != nil { - return err - } - - latencyEntries[string(k)] = e - - return nil - }) - - if err != nil { - return err - } - } - - return nil - }) - - if err != nil { - panic(err) - } - - return latencyEntries -} diff --git a/internal/project/framework/internals/middleware/bodydump.go b/internal/project/framework/internals/middleware/bodydump.go new file mode 100644 index 00000000..9af81537 --- /dev/null +++ b/internal/project/framework/internals/middleware/bodydump.go @@ -0,0 +1,100 @@ +package middleware + +import ( + "bytes" + "io" + "strconv" + "strings" + "sync" + "time" + + "github.com/labstack/echo/v4" + "github.com/labstack/gommon/color" + "github.com/labstack/gommon/log" + "github.com/valyala/fasttemplate" +) + +func BodyDumpWithCustomLogger(l *log.Logger) func(c echo.Context, reqBody, resBody []byte) { + format := `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` + + `"host":"${host}","method":"${method}","uri":"${uri}","status":${status},` + + `"request_body":${request_body},"response_body":${response_body}}` + "\n" + customTimeFormat := "2006-01-02 15:04:05.00000" + template := fasttemplate.New(format, "${", "}") + colorer := color.New() + colorer.SetOutput(l.Output()) + pool := &sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 256)) + }, + } + + return func(c echo.Context, reqBody, resBody []byte) { + buf := pool.Get().(*bytes.Buffer) + buf.Reset() + defer pool.Put(buf) + + if _, err := template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { + switch tag { + case "time_unix": + return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10)) + case "time_unix_nano": + return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10)) + case "time_rfc3339": + return buf.WriteString(time.Now().Format(time.RFC3339)) + case "time_rfc3339_nano": + return buf.WriteString(time.Now().Format(time.RFC3339Nano)) + case "time_custom": + return buf.WriteString(time.Now().Format(customTimeFormat)) + case "id": + id := c.Request().Header.Get(echo.HeaderXRequestID) + if id == "" { + id = c.Response().Header().Get(echo.HeaderXRequestID) + } + return buf.WriteString(id) + case "remote_ip": + return buf.WriteString(c.RealIP()) + case "host": + return buf.WriteString(c.Request().Host) + case "uri": + return buf.WriteString(c.Request().RequestURI) + case "method": + return buf.WriteString(c.Request().Method) + case "path": + p := c.Request().URL.Path + if p == "" { + p = "/" + } + return buf.WriteString(p) + case "status": + n := c.Response().Status + s := colorer.Green(n) + switch { + case n >= 500: + s = colorer.Red(n) + case n >= 400: + s = colorer.Yellow(n) + case n >= 300: + s = colorer.Cyan(n) + } + return buf.WriteString(s) + case "request_body": + if len(reqBody) > 0 { + return buf.Write(reqBody) + } + return buf.WriteString(`""`) + case "response_body": + return buf.WriteString(strings.TrimSuffix(string(resBody), "\n")) + } + return 0, nil + }); err != nil { + c.Logger().Error(err) + return + } + + if l.Output() == nil { + c.Logger().Output().Write(buf.Bytes()) + return + } + l.Output().Write(buf.Bytes()) + } +} diff --git a/internal/project/framework/internals/middleware/heartbeat.go b/internal/project/framework/internals/middleware/heartbeat.go deleted file mode 100644 index 531367d9..00000000 --- a/internal/project/framework/internals/middleware/heartbeat.go +++ /dev/null @@ -1,33 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package middleware - -import ( - "net/http" - "strings" - - "github.com/labstack/echo/v4" - "github.com/spf13/viper" -) - -// Heartbeat endpoint middleware useful to setting up a path like -// `/ping` that load balancer or uptime testing external services -// can make a request before hitting any routes. -func Heartbeat() echo.MiddlewareFunc { - - return func(next echo.HandlerFunc) echo.HandlerFunc { - - return func(c echo.Context) (err error) { - - request := c.Request() - - if request.Method == "GET" && strings.EqualFold(request.URL.Path, "/ping") { - projectName := viper.GetString("name") - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": projectName + ` πŸš€ pong`, - }) - } - - return next(c) - } - } -} diff --git a/internal/project/framework/internals/middleware/latency.go b/internal/project/framework/internals/middleware/latency.go deleted file mode 100644 index 85d7a432..00000000 --- a/internal/project/framework/internals/middleware/latency.go +++ /dev/null @@ -1,69 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package middleware - -import ( - "fmt" - "net/http" - "time" - - /**#bean*/ - "demo/framework/internals/async" - /*#bean.replace("{{ .PkgPath }}/framework/internals/async")**/ - /**#bean*/ - "demo/framework/internals/latency" - /*#bean.replace("{{ .PkgPath }}/framework/internals/latency")**/ - - "github.com/labstack/echo/v4" - "github.com/spf13/viper" -) - -// LatencyRecorder records the latency of each API endpoint. -func LatencyRecorder() echo.MiddlewareFunc { - - return func(next echo.HandlerFunc) echo.HandlerFunc { - - return func(c echo.Context) error { - - // Find the max ttl by intervals - var ttl int - - intervals := viper.GetIntSlice("http.uriLatencyIntervals") - for _, v := range intervals { - if v > ttl { - ttl = v - } - } - - req := c.Request() - res := c.Response() - start := time.Now() - - if err := next(c); err != nil { - return err - } - - stop := time.Now() - - // Only count successful response. - if res.Status == http.StatusOK { - l := stop.Sub(start) - t := time.Now().Unix() - uri := req.RequestURI - key := fmt.Sprintf("%s_%d", uri, t) - - apiLatency := latency.Entry{ - Latency: l, - Timestamp: t, - } - - // Async insert into badgerdb (key: uri, val: latency, ttl: ttl). - async.Execute(func(ctx echo.Context) { - latency.SetAPILatencyWithTTL(ctx, key, apiLatency, time.Duration(ttl)*time.Minute) - }) - } - - return nil - } - } - -} diff --git a/internal/project/framework/internals/middleware/server_stats.go b/internal/project/framework/internals/middleware/server_stats.go deleted file mode 100644 index c7c3a141..00000000 --- a/internal/project/framework/internals/middleware/server_stats.go +++ /dev/null @@ -1,134 +0,0 @@ -/**#bean*/ /*#bean.replace({{ .Copyright }})**/ -package middleware - -import ( - "net/http" - "sort" - "strings" - "sync" - "time" - - /**#bean*/ - ierror "demo/framework/internals/error" - /*#bean.replace(ierror "{{ .PkgPath }}/framework/internals/error")**/ - /**#bean*/ - "demo/framework/internals/latency" - /*#bean.replace("{{ .PkgPath }}/framework/internals/latency")**/ - - "github.com/labstack/echo/v4" - "github.com/spf13/viper" -) - -// ServerStats is an in-mermory data struct which stored different status of the server. -type ServerStats struct { - Uptime time.Time `json:"uptime"` - RequestCount uint64 `json:"requestCount"` - Latency map[string][]string `json:"latency"` - mutex sync.RWMutex -} - -// NewServerStats returns a `ServerStats`, normally being called in the server start up process. -func NewServerStats() *ServerStats { - return &ServerStats{ - Uptime: time.Now(), - Latency: map[string][]string{}, - } -} - -// GetServerStats is the handler of `/route/stats`. -func (s *ServerStats) GetServerStats(c echo.Context) error { - - adminClientID := viper.GetString("admin.clientId") - adminClientSecret := viper.GetString("admin.clientSecret") - - requestClientID := c.Request().Header.Get("Client-Id") - requestClientSecret := c.Request().Header.Get("Client-Secret") - - // Only admin a.k.a retail ai devops should able to hit this endpoint - if adminClientID == requestClientID && adminClientSecret == requestClientSecret { - - allowedMethod := viper.GetStringSlice("http.allowedMethod") - intervals := viper.GetIntSlice("http.uriLatencyIntervals") - - s.mutex.RLock() - defer s.mutex.RUnlock() - - m := latency.GetAllAPILatency(c) - - for _, r := range c.Echo().Routes() { - - if r.Path == "/" { - continue - } - - if strings.Contains(r.Name, "glob..func1") { - continue - } - - // XXX: IMPORTANT - `allowedMethod` has to be a sorted slice. - i := sort.SearchStrings(allowedMethod, r.Method) - - if i >= len(allowedMethod) || allowedMethod[i] != r.Method { - continue - } - - reducedLatency := latencyMapReduce(m, r.Path, intervals) - latencyHuman := make([]string, len(intervals)) - - for i, l := range reducedLatency { - latencyHuman[i] = l.String() - } - - s.Latency[r.Path] = latencyHuman - } - - // -------------- TEMP: also returning "/ping" stats -------------- - pingLatency := latencyMapReduce(m, "/ping", intervals) - latencyHuman := make([]string, len(intervals)) - for i, l := range pingLatency { - latencyHuman[i] = l.String() - } - s.Latency["/ping"] = latencyHuman - - return c.JSON(http.StatusOK, s) - } - - return c.JSON(http.StatusUnauthorized, map[string]interface{}{ - "errorCode": ierror.UNAUTHORIZED_ACCESS, - "errors": nil, - }) - -} - -func latencyMapReduce(m map[string]latency.Entry, uri string, intervals []int) []time.Duration { - - mappedEntries := []latency.Entry{} - - // Map the same uri request entries into one slice. - for k, e := range m { - if strings.HasPrefix(k, uri) { - mappedEntries = append(mappedEntries, e) - } - } - - // Reduce the records to different intervals according to the config. - // Example: [5mins, 10mins, 15mins] - reducedAvg := make([]time.Duration, len(intervals)) - - count := make([]int, len(intervals)) - - for _, e := range mappedEntries { - timestamp := time.Unix(e.Timestamp, 0) - now := time.Now() - - for idx, interval := range intervals { - if now.Sub(timestamp) < time.Minute*time.Duration(interval) { - // Calculate cumulative latency in every iteration to avoid overflow. - reducedAvg[idx] = reducedAvg[idx] + (e.Latency-reducedAvg[idx])/time.Duration(count[idx]+1) - count[idx]++ - } - } - } - - return reducedAvg -} diff --git a/internal/project/framework/internals/sentry/sentry.go b/internal/project/framework/internals/sentry/sentry.go index c7d81d28..4e80ef1a 100644 --- a/internal/project/framework/internals/sentry/sentry.go +++ b/internal/project/framework/internals/sentry/sentry.go @@ -5,10 +5,6 @@ import ( "errors" "reflect" - /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ - "github.com/getsentry/sentry-go" sentryecho "github.com/getsentry/sentry-go/echo" "github.com/labstack/echo/v4" @@ -27,9 +23,9 @@ func PushData(c echo.Context, data interface{}, event *sentry.Event, isAsync boo if !isSentry || sentryDSN == "" { if data, ok := data.(error); ok { - global.EchoInstance.Logger.Error(data) + c.Echo().Logger.Error(data) } else { - global.EchoInstance.Logger.Info(data) + c.Echo().Logger.Info(data) } return diff --git a/internal/project/framework/kernel/kernel.go b/internal/project/framework/kernel/kernel.go index 5676a5ec..5cbaf690 100644 --- a/internal/project/framework/kernel/kernel.go +++ b/internal/project/framework/kernel/kernel.go @@ -13,9 +13,6 @@ import ( "demo/framework/internals/binder" /*#bean.replace("{{ .PkgPath }}/framework/internals/binder")**/ /**#bean*/ - "demo/framework/internals/helpers" - /*#bean.replace("{{ .PkgPath }}/framework/internals/helpers")**/ - /**#bean*/ imiddleware "demo/framework/internals/middleware" /*#bean.replace(imiddleware "{{ .PkgPath }}/framework/internals/middleware")**/ /**#bean*/ @@ -30,6 +27,7 @@ import ( "github.com/getsentry/sentry-go" sentryecho "github.com/getsentry/sentry-go/echo" + "github.com/google/uuid" "github.com/labstack/echo-contrib/prometheus" "github.com/labstack/echo/v4" echomiddleware "github.com/labstack/echo/v4/middleware" @@ -44,75 +42,108 @@ func NewEcho() *echo.Echo { // Hide default `Echo` banner during startup. e.HideBanner = true - // IMPORTANT: Time out middleware. It has to be the first middleware to initialize. - e.Use(imiddleware.RequestTimeout(viper.GetDuration("http.timeout") * time.Second)) + // Set custom request binder + e.Binder = &binder.CustomBinder{} + + // Setup HTML view templating engine. + viewsTemplateCache := viper.GetBool("html.viewsTemplateCache") + e.Renderer = echoview.New(goview.Config{ + Root: "views", + Extension: ".html", + Master: "templates/master", + Partials: []string{}, + Funcs: make(template.FuncMap), + DisableCache: !viewsTemplateCache, + Delims: goview.Delims{Left: "{{`{{`}}", Right: "{{`}}`}}"}, + }) // Get log type (file or stdout) settings from config. debugLogLocation := viper.GetString("debugLog") requestLogLocation := viper.GetString("requestLog") - // bodydumpLogLocation := viper.GetString("bodydumpLog") + bodydumpLogLocation := viper.GetString("bodydumpLog") - // IMPORTANT: Set debug log output location. + // IMPORTANT: Different types of Loggers + // Set debug log output location. if debugLogLocation != "" { - file, err := os.OpenFile(debugLogLocation, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + file, err := openFile(debugLogLocation) if err != nil { e.Logger.Fatalf("Unable to open log file: %v Server πŸš€ crash landed. Exiting...\n", err) } e.Logger.SetOutput(file) } + e.Logger.SetLevel(log.DEBUG) + e.Logger.Info("ENVIRONMENT: ", viper.GetString("environment")) - reqLoggerConfig := echomiddleware.LoggerConfig{ - Format: helpers.JsonLogFormat(), // we need additional access log parameter - } - - // IMPORTANT: Set request log output location. + // Set request log output location. (request logger is using the same echo logger but with different config) + requestLoggerConfig := echomiddleware.LoggerConfig{} if requestLogLocation != "" { - file, err := os.OpenFile(debugLogLocation, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + file, err := openFile(requestLogLocation) if err != nil { e.Logger.Fatalf("Unable to open log file: %v Server πŸš€ crash landed. Exiting...\n", err) } - reqLoggerConfig.Output = file + requestLoggerConfig.Output = file } + requestLogger := echomiddleware.LoggerWithConfig(requestLoggerConfig) + e.Use(requestLogger) - reqLogger := echomiddleware.LoggerWithConfig(reqLoggerConfig) - - e.Use(reqLogger) - - e.Logger.SetLevel(log.DEBUG) - e.Logger.Info("ENVIRONMENT: ", viper.GetString("environment")) + // Set bodydump log output location. (bodydumper using a custom logger to aovid overwriting the setting of the default logger) + bodydumpLogger := log.New("bodydump") + if bodydumpLogLocation != "" { + file, err := openFile(bodydumpLogLocation) + if err != nil { + if err != nil { + e.Logger.Fatalf("Unable to open log file: %v Server πŸš€ crash landed. Exiting...\n", err) + } + } + bodydumpLogger.SetOutput(file) + } + bodydumper := echomiddleware.BodyDumpWithConfig(echomiddleware.BodyDumpConfig{ + Handler: imiddleware.BodyDumpWithCustomLogger(bodydumpLogger), + }) + e.Use(bodydumper) // Some pre-build middleware initialization. e.Pre(echomiddleware.RemoveTrailingSlash()) + if viper.GetBool("http.isHttpsRedirect") { + e.Pre(echomiddleware.HTTPSRedirect()) + } e.Use(echomiddleware.Recover()) - // Enable prometheus metrics middleware. Metrics data should be accessed via `/metrics` endpoint. - // This will help us to integrate `bean's` health into `k8s`. - isPrometheusMetrics := viper.GetBool("prometheus.isPrometheusMetrics") - if isPrometheusMetrics { - p := prometheus.NewPrometheus("echo", prometheusUrlSkipper) - p.Use(e) - } + // IMPORTANT: Request related middleware. + // Time out middleware. + e.Use(imiddleware.RequestTimeout(viper.GetDuration("http.timeout") * time.Second)) - // Setup HTML view templating engine. - viewsTemplateCache := viper.GetBool("html.viewsTemplateCache") + // Set the `X-Request-ID` header field if it doesn't exist. + e.Use(echomiddleware.RequestIDWithConfig(echomiddleware.RequestIDConfig{ + Generator: uuid.NewString, + })) - e.Renderer = echoview.New(goview.Config{ - Root: "views", - Extension: ".html", - Master: "templates/master", - Partials: []string{}, - Funcs: make(template.FuncMap), - DisableCache: !viewsTemplateCache, - Delims: goview.Delims{Left: "{{`{{`}}", Right: "{{`}}`}}"}, - }) + // Adds a `Server` header to the response. + e.Use(imiddleware.ServerHeader(viper.GetString("name"), viper.GetString("version"))) + + // Sets the maximum allowed size for a request body, return `413 - Request Entity Too Large` if the size exceeds the limit. + e.Use(echomiddleware.BodyLimit(viper.GetString("http.bodyLimit"))) - // CORS initialization and support only HTTP methods which are configured under `http.allowedMethod` - // parameters in `env.json`. + // CORS initialization and support only HTTP methods which are configured under `http.allowedMethod` parameters in `env.json`. e.Use(echomiddleware.CORSWithConfig(echomiddleware.CORSConfig{ AllowOrigins: []string{"*"}, AllowMethods: viper.GetStringSlice("http.allowedMethod"), })) + // Basic HTTP headers security like XSS protection... + e.Use(echomiddleware.SecureWithConfig(echomiddleware.SecureConfig{ + XSSProtection: viper.GetString("security.http.header.xssProtection"), // Adds the X-XSS-Protection header with the value `1; mode=block`. + ContentTypeNosniff: viper.GetString("security.http.header.contentTypeNosniff"), // Adds the X-Content-Type-Options header with the value `nosniff`. + XFrameOptions: viper.GetString("security.http.header.xFrameOptions"), // The X-Frame-Options header value to be set with a custom value. + HSTSMaxAge: viper.GetInt("security.http.header.hstsMaxAge"), // HSTS header is only included when the connection is HTTPS. + ContentSecurityPolicy: viper.GetString("security.http.header.contentSecurityPolicy"), // Allows the Content-Security-Policy header value to be set with a custom value. + })) + + // Return `405 Method Not Allowed` if a wrong HTTP method been called for an API route. + // Return `404 Not Found` if a wrong API route been called. + e.Use(imiddleware.MethodNotAllowedAndRouteNotFound()) + + // IMPORTANT: Capturing error and send to sentry if needed. // Sentry `panic` error handler and APM initialization if activated from `env.json` isSentry := viper.GetBool("sentry.isSentry") if isSentry { @@ -145,56 +176,13 @@ func NewEcho() *echo.Echo { })) } - // Dump request body for logging purpose if activated from `env.json` - isBodyDump := viper.GetBool("isBodyDump") - if isBodyDump { - bodyDumper := echomiddleware.BodyDumpWithConfig(echomiddleware.BodyDumpConfig{ - Handler: helpers.BodyDumpHandler, - }) - - e.Use(bodyDumper) - } - - // Body limit middleware sets the maximum allowed size for a request body, - // if the size exceeds the configured limit, it sends β€œ413 - Request Entity Too Large” response. - e.Use(echomiddleware.BodyLimit(viper.GetString("http.bodyLimit"))) - - // ---------- HTTP headers security for XSS protection and alike ---------- - e.Use(echomiddleware.SecureWithConfig(echomiddleware.SecureConfig{ - XSSProtection: viper.GetString("security.http.header.xssProtection"), // Adds the X-XSS-Protection header with the value `1; mode=block`. - ContentTypeNosniff: viper.GetString("security.http.header.contentTypeNosniff"), // Adds the X-Content-Type-Options header with the value `nosniff`. - XFrameOptions: viper.GetString("security.http.header.xFrameOptions"), // The X-Frame-Options header value to be set with a custom value. - HSTSMaxAge: viper.GetInt("security.http.header.hstsMaxAge"), // HSTS header is only included when the connection is HTTPS. - ContentSecurityPolicy: viper.GetString("security.http.header.contentSecurityPolicy"), // Allows the Content-Security-Policy header value to be set with a custom value. - })) - // ---------- HTTP headers security for XSS protection and alike ---------- - - // ------ HTTPS redirect middleware redirects http requests to https ------ - isHttpsRedirect := viper.GetBool("http.isHttpsRedirect") - if isHttpsRedirect { - e.Pre(echomiddleware.HTTPSRedirect()) + // Enable prometheus metrics middleware. Metrics data should be accessed via `/metrics` endpoint. + // This will help us to integrate `bean's` health into `k8s`. + isPrometheusMetrics := viper.GetBool("prometheus.isPrometheusMetrics") + if isPrometheusMetrics { + p := prometheus.NewPrometheus("echo", prometheusUrlSkipper) + p.Use(e) } - // ------ HTTPS redirect middleware redirects http requests to https ------ - - // Return `405 Method Not Allowed` if a wrong HTTP method been called for an API route. - // Return `404 Not Found` if a wrong API route been called. - e.Use(imiddleware.MethodNotAllowedAndRouteNotFound()) - - // -------------- Special Middleware And Controller To Get Server Stats -------------- - serverStats := imiddleware.NewServerStats() - e.GET("/route/stats", serverStats.GetServerStats) - // -------------- Special Middleware And Controller To Get Server Stats -------------- - - // Adds a `Server` header to the response. - name := viper.GetString("name") - version := viper.GetString("version") - e.Use(imiddleware.ServerHeader(name, version)) - - // `/ping` uri to response a `pong`. - e.Use(imiddleware.Heartbeat()) - - // Set custom request binder - e.Binder = &binder.CustomBinder{} return e } @@ -210,13 +198,14 @@ func prometheusUrlSkipper(c echo.Context) bool { // openFile opens and return the file, if doesn't exist, create it, or append to the file with the directory. func openFile(path string) (*os.File, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - if err := os.MkdirAll(filepath.Dir(path), 0754); err != nil { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(filepath.Dir(path), 0764); err != nil { + return nil, err + } + } else { return nil, err } - } else { - return nil, err } - return os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0664) } diff --git a/internal/project/handlers/mytest.go b/internal/project/handlers/mytest.go index 6bd28b1e..288ac3ab 100644 --- a/internal/project/handlers/mytest.go +++ b/internal/project/handlers/mytest.go @@ -5,6 +5,9 @@ import ( "net/http" "time" + /**#bean*/ + "demo/framework/internals/async" + /*#bean.replace("{{ .PkgPath }}/framework/internals/async")**/ /**#bean*/ "demo/services" /*#bean.replace("{{ .PkgPath }}/services")**/ @@ -27,13 +30,17 @@ func NewMyTestHandler(myTestSvc services.MyTestService) *myTestHandler { func (handler *myTestHandler) MyTestJSONIndex(c echo.Context) error { - res, _ := handler.myTestService.GetMasterSQLTableList(c) + dbName, err := handler.myTestService.GetMasterSQLTableList(c) + if err != nil { + return err + } - // IMPORTANT: This is how you can log something. - c.Logger().Info(res["dbName"]) + async.Execute(func(c echo.Context) { + c.Logger().Debug(dbName) + }, c.Echo()) - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": "Howdy! I am {{ .PkgName }} πŸš€ ", + return c.JSON(http.StatusOK, map[string]string{ + "dbName": dbName, }) } diff --git a/internal/project/main.go b/internal/project/main.go index 73282b55..f1eadf89 100644 --- a/internal/project/main.go +++ b/internal/project/main.go @@ -1,10 +1,7 @@ /**#bean*/ /*#bean.replace({{ .Copyright }})**/ package main -import ( - /**#bean*/ - "demo/commands" - /*#bean.replace("{{ .PkgName }}/commands")**/) +import /**#bean*/ "demo/commands" /*#bean.replace("{{ .PkgName }}/commands")**/ func main() { commands.Execute() diff --git a/internal/project/repositories/infra.go b/internal/project/repositories/infra.go index 3bde1d99..87d799b9 100644 --- a/internal/project/repositories/infra.go +++ b/internal/project/repositories/infra.go @@ -1,14 +1,11 @@ /**#bean*/ /*#bean.replace({{ .Copyright }})**/ package repositories -import ( - /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/) +import /**#bean*/ "demo/framework/bean" /*#bean.replace("{{ .PkgPath }}/framework/bean")**/ // IMPORTANT: DO NOT DELETE THIS `DbInfra` struct. THIS WILL BE USED IN EVERY SINGLE REPOSITORY // FILE YOU CREATE. `DbInfra` IS HOLDING ALL KINDS OF DATABASE INFRASTRUCTURE WHICH YOU CONFUGURED THROUGH // `env.json`. type DbInfra struct { - Conn *global.DBDeps + Conn *bean.DBDeps } diff --git a/internal/project/repositories/mytest.go b/internal/project/repositories/mytest.go index b01353b2..5be4ae60 100644 --- a/internal/project/repositories/mytest.go +++ b/internal/project/repositories/mytest.go @@ -3,25 +3,20 @@ package repositories import ( /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ + "demo/framework/bean" + /*#bean.replace("{{ .PkgPath }}/framework/bean")**/ "github.com/labstack/echo/v4" ) type MyTestRepository interface { - GetMasterSQLTableList(c echo.Context) (map[string]interface{}, error) + GetMasterSQLTableName(c echo.Context) (string, error) } -func NewMyTestRepository(dbDeps *global.DBDeps) *DbInfra { +func NewMyTestRepository(dbDeps *bean.DBDeps) *DbInfra { return &DbInfra{dbDeps} } -func (db *DbInfra) GetMasterSQLTableList(c echo.Context) (map[string]interface{}, error) { - - mysqlDbName := db.Conn.MasterMySQLDBName - - return map[string]interface{}{ - "dbName": mysqlDbName, - }, nil +func (db *DbInfra) GetMasterSQLTableName(c echo.Context) (string, error) { + return db.Conn.MasterMySQLDBName, nil } diff --git a/internal/project/routers/route.go b/internal/project/routers/route.go index 92cb5acb..78815fee 100644 --- a/internal/project/routers/route.go +++ b/internal/project/routers/route.go @@ -2,9 +2,11 @@ package routers import ( + "net/http" + /**#bean*/ - "demo/framework/internals/global" - /*#bean.replace("{{ .PkgPath }}/framework/internals/global")**/ + "demo/framework/bean" + /*#bean.replace("{{ .PkgPath }}/framework/bean")**/ /**#bean*/ "demo/handlers" /*#bean.replace("{{ .PkgPath }}/handlers")**/ @@ -13,7 +15,10 @@ import ( /*#bean.replace("{{ .PkgPath }}/repositories")**/ /**#bean*/ "demo/services" - /*#bean.replace("{{ .PkgPath }}/services")**/) + /*#bean.replace("{{ .PkgPath }}/services")**/ + + "github.com/labstack/echo/v4" +) type Repositories struct { MyTestRepo repositories.MyTestRepository @@ -27,12 +32,12 @@ type Handlers struct { MyTestHdlr handlers.MyTestHandler } -func Init() { +func Init(b *bean.Bean) { - e := global.EchoInstance + e := b.Echo repos := &Repositories{ - MyTestRepo: repositories.NewMyTestRepository(global.DBConn), + MyTestRepo: repositories.NewMyTestRepository(b.DBConn), } svcs := &Services{ @@ -43,12 +48,19 @@ func Init() { MyTestHdlr: handlers.NewMyTestHandler(svcs.MyTestSvc), } + // Default index page goes to above JSON (/json) index page. + e.GET("/", hdlrs.MyTestHdlr.MyTestJSONIndex) + // IMPORTANT: Just a JSON response index page. Please change or update it if you want. e.GET("/json", hdlrs.MyTestHdlr.MyTestJSONIndex) // IMPORTANT: Just a HTML response index page. Please change or update it if you want. e.GET("/html", hdlrs.MyTestHdlr.MyTestHTMLIndex) - // Default index page goes to above JSON (/json) index page. - e.GET("/", hdlrs.MyTestHdlr.MyTestJSONIndex) + // TODO: Maybe don't need this neither. + e.GET("/ping", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": `{{ .PkgName }} πŸš€ pong`, + }) + }) } diff --git a/internal/project/services/mytest.go b/internal/project/services/mytest.go index 9659d8ff..7e410baf 100644 --- a/internal/project/services/mytest.go +++ b/internal/project/services/mytest.go @@ -10,7 +10,7 @@ import ( ) type MyTestService interface { - GetMasterSQLTableList(c echo.Context) (map[string]interface{}, error) + GetMasterSQLTableList(c echo.Context) (string, error) } type myTestService struct { @@ -21,7 +21,6 @@ func NewMyTestService(myTestRepo repositories.MyTestRepository) *myTestService { return &myTestService{myTestRepo} } -func (service *myTestService) GetMasterSQLTableList(c echo.Context) (map[string]interface{}, error) { - - return service.myTestRepository.GetMasterSQLTableList(c) +func (service *myTestService) GetMasterSQLTableList(c echo.Context) (string, error) { + return service.myTestRepository.GetMasterSQLTableName(c) }