diff --git a/README.md b/README.md index 9e55ca6..3767e80 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # golang API Skeleton -[![Build Status](https://drone.devices.local/api/badges/mawas/golang-api-skeleton/status.svg)](https://drone.devices.local/mawas/golang-api-skeleton)![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-77%25-brightgreen.svg?longCache=true&style=flat) +[![Build Status](https://drone.devices.local/api/badges/mawas/golang-api-skeleton/status.svg)](https://drone.devices.local/mawas/golang-api-skeleton)![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-67%25-brightgreen.svg?longCache=true&style=flat) refined skeleton future apis should be based on diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..44fea59 --- /dev/null +++ b/api/api.go @@ -0,0 +1,15 @@ +package api + +import ( + v1 "git.devices.local/mawas/golang-api-skeleton/api/v1" + "github.com/gin-gonic/gin" +) + +// ApplyRoutes applies router to gin Router +func ApplyRoutes(r *gin.Engine) *gin.Engine { + api := r.Group("/api") + { + v1.ApplyRoutes(api) + } + return r +} diff --git a/api/v1/v1.go b/api/v1/v1.go new file mode 100644 index 0000000..c552ff3 --- /dev/null +++ b/api/v1/v1.go @@ -0,0 +1,11 @@ +package v1 + +import "github.com/gin-gonic/gin" + +func ApplyRoutes(r *gin.RouterGroup) *gin.RouterGroup { + v1 := r.Group("/v1") + { + v1.GET("/ping") + } + return v1 +} diff --git a/cmd/add/add.go b/cmd/add/add.go new file mode 100644 index 0000000..a325707 --- /dev/null +++ b/cmd/add/add.go @@ -0,0 +1,23 @@ +package add + +import ( + "git.devices.local/mawas/golang-api-skeleton/cmd/add/admin" + "github.com/spf13/cobra" +) + +func Command(appName string, cfgFile string, flagCfg map[string]interface{}) *cobra.Command { + cmd := &cobra.Command{ + Use: "add", + Short: "Allows to add items", + Long: "Allows to add items", + // SilenceUsage: true, + // SilenceErrors: true, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Help() + return nil + }, + } + cmd.AddCommand(admin.Command(appName, cfgFile, flagCfg)) + return cmd +} diff --git a/cmd/add/admin/admin.go b/cmd/add/admin/admin.go new file mode 100644 index 0000000..14428d9 --- /dev/null +++ b/cmd/add/admin/admin.go @@ -0,0 +1,68 @@ +package admin + +import ( + "fmt" + + "git.devices.local/mawas/golang-api-skeleton/lib/common" + "git.devices.local/mawas/golang-api-skeleton/lib/config" + "git.devices.local/mawas/golang-api-skeleton/lib/database" + "git.devices.local/mawas/golang-api-skeleton/models" + "git.devices.local/mawas/golang-api-skeleton/repositories" + "git.devices.local/mawas/golang-api-skeleton/services" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func Command(appName string, cfgFile string, flagCfg map[string]interface{}) *cobra.Command { + var username, firstname, lastname, email string + cmd := &cobra.Command{ + Use: "admin", + Short: "Create admin user if non existent", + Long: "Create admin user if non existent", + // SilenceUsage: true, + // SilenceErrors: true, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return createAdminUser(appName, cfgFile, flagCfg, username, firstname, lastname, email) + }, + } + cmd.Flags().StringVarP(&username, "username", "U", "admin", "desired admin user name") + cmd.Flags().StringVarP(&firstname, "firstname", "F", "", "admin user fistname") + if err := cmd.MarkFlagRequired("firstname"); err != nil { + // cmd.Usage() + } + cmd.Flags().StringVarP(&lastname, "lastname", "L", "", "admin user lastname") + cmd.MarkFlagRequired("lastname") + cmd.Flags().StringVarP(&email, "email", "M", "", "admin user email address") + cmd.MarkFlagRequired("email") + return cmd +} + +func createAdminUser(appName, cfgFile string, flagCfg map[string]interface{}, username, firstname, lastname, email string) error { + if err := config.Initialize(appName, cfgFile, flagCfg); err != nil { + return fmt.Errorf("config:%v", err) + } + db, err := database.Connect(database.Credentials{ + Host: viper.GetString("database.host"), + Port: viper.GetInt("database.port"), + Dialect: viper.GetString("database.dialect"), + Database: viper.GetString("database.database"), + User: viper.GetString("database.user"), + Password: viper.GetString("database.password"), + MaxOpenConn: viper.GetInt("database.port"), + MaxIdleConn: viper.GetInt("database.port"), + MaxLifeTime: viper.GetInt("database.port"), + Debug: true, + }) + if err != nil { + return fmt.Errorf("database:%v", err) + } + if err := db.AutoMigrate(&models.User{}); err != nil { + return fmt.Errorf("database:%v", err) + } + userRepo := repositories.NewUserRepository(db, common.CLIUserID, common.CLIUsername, nil) + userService := services.NewUserService(userRepo) + adminUser := services.NewUser(username, firstname, lastname, email) + userService.Create(&adminUser) + return nil +} diff --git a/cmd/cmd.go b/cmd/cmd.go deleted file mode 100644 index ec2d416..0000000 --- a/cmd/cmd.go +++ /dev/null @@ -1,132 +0,0 @@ -package cmd - -import ( - "os" - - "git.devices.local/mawas/golang-api-skeleton/lib/config" - "github.com/spf13/cobra" -) - -// TODO add shell completion - -func newRootCmd(appName string, appDescription string) *cobra.Command { - return &cobra.Command{ - Use: appName, - Short: appDescription, - Long: appDescription, - // SilenceUsage: true, - // SilenceErrors: true, - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - }, - } -} - -func newVersionCmd(version string, appName string) *cobra.Command { - return &cobra.Command{ - Use: "version", - Aliases: []string{"Version"}, - Short: "Print the version number", - Long: "Print the version number", - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - cmd.Println(appName, "version", version) - os.Exit(0) - }, - } -} - -func newSampleCfgCmd() *cobra.Command { - return &cobra.Command{ - Use: "sample-config", - Short: "Print sample config", - Long: "Print an example configuration file content", - Run: func(cmd *cobra.Command, args []string) { - cmd.Println(config.Sample) - os.Exit(0) - }, - } -} - -func newSampleCLICmd() *cobra.Command { - return &cobra.Command{ - Use: "cli", - Short: "Interactive CLI", - Long: "Interactive CLI maybe", - Run: func(cmd *cobra.Command, args []string) { - cmd.Println("here might be an cli") - os.Exit(0) - }, - } -} - -func Initialize(version string, appName string, appDescription string) (string, map[string]interface{}, error) { - var cfgFile string - rootCmd := newRootCmd(appName, appDescription) - rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - if err := cmd.Usage(); err != nil { - os.Exit(1) - } - os.Exit(0) - }) - rootCmd.AddCommand(newVersionCmd(version, appName)) - rootCmd.AddCommand(newSampleCfgCmd()) - // rootCmd.AddCommand(newSampleCLICmd()) - - rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file") - flagCfg := map[string]interface{}{ - "application.environment": rootCmd.Flags().StringP("environment", "e", "", "application environment"), - "application.listenaddress": rootCmd.Flags().StringP("listenaddress", "l", "", "application listenaddress or host"), - "application.port": rootCmd.Flags().IntP("port", "p", 0, "application listenport"), - "database.host": rootCmd.Flags().StringP("database-host", "H", "", "database host"), - "database.port": rootCmd.Flags().IntP("database-port", "P", 0, "database port"), - "database.dialect": rootCmd.Flags().StringP("database-dialect", "D", "", "database dialect"), - "database.database": rootCmd.Flags().StringP("database-name", "d", "", "database name"), - "database.user": rootCmd.Flags().StringP("database-user", "u", "", "database user"), - "database.password": rootCmd.Flags().StringP("database-password", "s", "", "database password"), - "database.maxopenconn": rootCmd.Flags().IntP("database-maxopen", "o", 0, "database max open connections"), - "database.maxidleconn": rootCmd.Flags().IntP("database-maxidle", "i", 0, "database max idle connections"), - "database.maxlifetime": rootCmd.Flags().IntP("database-maxlifetime", "t", 0, "database max connection lifetime"), - } - if err := rootCmd.Execute(); err != nil { - return cfgFile, flagCfg, err - } - return cfgFile, flagCfg, nil -} - -// func Initialize(version string, appName string, appDescription string) error { -// var cfgFile string -// rootCmd := newRootCmd(appName, appDescription) -// rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { -// if err := cmd.Usage(); err != nil { -// os.Exit(1) -// } -// os.Exit(0) -// }) -// rootCmd.AddCommand(newVersionCmd(version, appName)) -// rootCmd.AddCommand(newSampleCfgCmd()) -// // rootCmd.AddCommand(newSampleCLICmd()) -// -// rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file") -// flagCfg := map[string]interface{}{ -// "application.environment": rootCmd.Flags().StringP("environment", "e", "", "application environment"), -// "application.listenaddress": rootCmd.Flags().StringP("listenaddress", "l", "", "application listenaddress or host"), -// "application.port": rootCmd.Flags().IntP("port", "p", 0, "application listenport"), -// "database.host": rootCmd.Flags().StringP("database-host", "H", "", "database host"), -// "database.port": rootCmd.Flags().IntP("database-port", "P", 0, "database port"), -// "database.dialect": rootCmd.Flags().StringP("database-dialect", "D", "", "database dialect"), -// "database.database": rootCmd.Flags().StringP("database-name", "d", "", "database name"), -// "database.user": rootCmd.Flags().StringP("database-user", "u", "", "database user"), -// "database.password": rootCmd.Flags().StringP("database-password", "s", "", "database password"), -// "database.maxopenconn": rootCmd.Flags().IntP("database-maxopen", "o", 0, "database max open connections"), -// "database.maxidleconn": rootCmd.Flags().IntP("database-maxidle", "i", 0, "database max idle connections"), -// "database.maxlifetime": rootCmd.Flags().IntP("database-maxlifetime", "t", 0, "database max connection lifetime"), -// } -// if err := rootCmd.Execute(); err != nil { -// return err -// } -// if err := config.Initialize(appName, cfgFile, flagCfg); err != nil { -// return err -// } -// return nil -// } diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..e04eb5d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,199 @@ +package cmd + +import ( + "os" + + "git.devices.local/mawas/golang-api-skeleton/cmd/add" + "git.devices.local/mawas/golang-api-skeleton/cmd/server" + "git.devices.local/mawas/golang-api-skeleton/lib/config" + "github.com/spf13/cobra" +) + +// TODO add shell completion + +func newRootCmd(appName string, appDescription string) *cobra.Command { + return &cobra.Command{ + Use: appName, + Short: appDescription, + Long: appDescription, + // SilenceUsage: true, + // SilenceErrors: true, + DisableAutoGenTag: true, + Run: func(cmd *cobra.Command, args []string) { + }, + } +} + +func NewRootCmd(version string, appName string, appDescription string) *cobra.Command { + cmd := &cobra.Command{ + Use: appName, + Short: appDescription, + Long: appDescription, + // SilenceUsage: true, + // SilenceErrors: true, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Usage() + return nil + }, + } + // cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + // if err := cmd.Usage(); err != nil { + // os.Exit(1) + // } + // os.Exit(0) + // }) + cmd.AddCommand(newVersionCmd(version, appName)) + cmd.AddCommand(newSampleCfgCmd()) + cfgFile, flagCfg := getFlags(cmd) + cmd.SetHelpCommand(add.Command(appName, cfgFile, flagCfg)) + cmd.AddCommand(server.Command(appName, cfgFile, flagCfg)) + return cmd +} + +func newVersionCmd(version string, appName string) *cobra.Command { + return &cobra.Command{ + Use: "version", + Aliases: []string{"Version"}, + Short: "Print the version number", + Long: "Print the version number", + DisableAutoGenTag: true, + Run: func(cmd *cobra.Command, args []string) { + cmd.Println(appName, "version", version) + // os.Exit(0) + }, + } +} + +func newSampleCfgCmd() *cobra.Command { + return &cobra.Command{ + Use: "sample-config", + Short: "Print sample config", + Long: "Print an example configuration file content", + Run: func(cmd *cobra.Command, args []string) { + cmd.Println(config.Sample) + os.Exit(0) + }, + } +} + +//func createAdminCmd(userService *services.UserService) *cobra.Command { +// return &cobra.Command{ +// Use: "admin", +// Short: "Create initial admin user", +// Long: "Create initial admin user", +// RunE: func(cmd *cobra.Command, args []string) error{ +// adminUser := &models.Token{ +// ExpiresAt: time.Now().Add(24 * time.Hour), +// UserID: userID, +// Active: true, +// } +// if result1, err := tokenService.Create(token1); err != nil { +// panic(err) +// } +// }, +// } +//} + +func newSampleCLICmd() *cobra.Command { + return &cobra.Command{ + Use: "cli", + Short: "Interactive CLI", + Long: "Interactive CLI maybe", + Run: func(cmd *cobra.Command, args []string) { + cmd.Println("here might be an cli") + os.Exit(0) + }, + } +} + +func Initialize(version string, appName string, appDescription string) (string, map[string]interface{}, error) { + var cfgFile string + rootCmd := newRootCmd(appName, appDescription) + rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + if err := cmd.Usage(); err != nil { + os.Exit(1) + } + os.Exit(0) + }) + rootCmd.AddCommand(newVersionCmd(version, appName)) + rootCmd.AddCommand(newSampleCfgCmd()) + rootCmd.AddCommand(newSampleCfgCmd()) + // rootCmd.AddCommand(newSampleCLICmd()) + + rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file") + flagCfg := map[string]interface{}{ + "application.environment": rootCmd.PersistentFlags().StringP("environment", "e", "", "application environment"), + "application.listenaddress": rootCmd.PersistentFlags().StringP("listenaddress", "l", "", "application listenaddress or host"), + "application.port": rootCmd.PersistentFlags().IntP("port", "p", 0, "application listenport"), + "database.host": rootCmd.PersistentFlags().StringP("database-host", "H", "", "database host"), + "database.port": rootCmd.PersistentFlags().IntP("database-port", "P", 0, "database port"), + "database.dialect": rootCmd.PersistentFlags().StringP("database-dialect", "D", "", "database dialect"), + "database.database": rootCmd.PersistentFlags().StringP("database-name", "d", "", "database name"), + "database.user": rootCmd.PersistentFlags().StringP("database-user", "u", "", "database user"), + "database.password": rootCmd.PersistentFlags().StringP("database-password", "s", "", "database password"), + "database.maxopenconn": rootCmd.PersistentFlags().IntP("database-maxopen", "o", 0, "database max open connections"), + "database.maxidleconn": rootCmd.PersistentFlags().IntP("database-maxidle", "i", 0, "database max idle connections"), + "database.maxlifetime": rootCmd.PersistentFlags().IntP("database-maxlifetime", "t", 0, "database max connection lifetime"), + } + if err := rootCmd.Execute(); err != nil { + return cfgFile, flagCfg, err + } + return cfgFile, flagCfg, nil +} + +func getFlags(cmd *cobra.Command) (cfgFile string, flagCfg map[string]interface{}) { + cmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file") + flagCfg = map[string]interface{}{ + "application.environment": cmd.PersistentFlags().StringP("environment", "e", "", "application environment"), + "application.listenaddress": cmd.PersistentFlags().StringP("listenaddress", "l", "", "application listenaddress or host"), + "application.port": cmd.PersistentFlags().IntP("port", "p", 0, "application listenport"), + "database.host": cmd.PersistentFlags().StringP("database-host", "H", "", "database host"), + "database.port": cmd.PersistentFlags().IntP("database-port", "P", 0, "database port"), + "database.dialect": cmd.PersistentFlags().StringP("database-dialect", "D", "", "database dialect"), + "database.database": cmd.PersistentFlags().StringP("database-name", "d", "", "database name"), + "database.user": cmd.PersistentFlags().StringP("database-user", "u", "", "database user"), + "database.password": cmd.PersistentFlags().StringP("database-password", "s", "", "database password"), + "database.maxopenconn": cmd.PersistentFlags().IntP("database-maxopen", "o", 0, "database max open connections"), + "database.maxidleconn": cmd.PersistentFlags().IntP("database-maxidle", "i", 0, "database max idle connections"), + "database.maxlifetime": cmd.PersistentFlags().IntP("database-maxlifetime", "t", 0, "database max connection lifetime"), + } + return cfgFile, flagCfg +} + +// func Initialize(version string, appName string, appDescription string) error { +// var cfgFile string +// rootCmd := newRootCmd(appName, appDescription) +// rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { +// if err := cmd.Usage(); err != nil { +// os.Exit(1) +// } +// os.Exit(0) +// }) +// rootCmd.AddCommand(newVersionCmd(version, appName)) +// rootCmd.AddCommand(newSampleCfgCmd()) +// // rootCmd.AddCommand(newSampleCLICmd()) +// +// rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file") +// flagCfg := map[string]interface{}{ +// "application.environment": rootCmd.Flags().StringP("environment", "e", "", "application environment"), +// "application.listenaddress": rootCmd.Flags().StringP("listenaddress", "l", "", "application listenaddress or host"), +// "application.port": rootCmd.Flags().IntP("port", "p", 0, "application listenport"), +// "database.host": rootCmd.Flags().StringP("database-host", "H", "", "database host"), +// "database.port": rootCmd.Flags().IntP("database-port", "P", 0, "database port"), +// "database.dialect": rootCmd.Flags().StringP("database-dialect", "D", "", "database dialect"), +// "database.database": rootCmd.Flags().StringP("database-name", "d", "", "database name"), +// "database.user": rootCmd.Flags().StringP("database-user", "u", "", "database user"), +// "database.password": rootCmd.Flags().StringP("database-password", "s", "", "database password"), +// "database.maxopenconn": rootCmd.Flags().IntP("database-maxopen", "o", 0, "database max open connections"), +// "database.maxidleconn": rootCmd.Flags().IntP("database-maxidle", "i", 0, "database max idle connections"), +// "database.maxlifetime": rootCmd.Flags().IntP("database-maxlifetime", "t", 0, "database max connection lifetime"), +// } +// if err := rootCmd.Execute(); err != nil { +// return err +// } +// if err := config.Initialize(appName, cfgFile, flagCfg); err != nil { +// return err +// } +// return nil +// } diff --git a/cmd/cmd_test.go b/cmd/root_test.go similarity index 100% rename from cmd/cmd_test.go rename to cmd/root_test.go diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 0000000..5b39ffc --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,68 @@ +package server + +import ( + "fmt" + + "git.devices.local/mawas/golang-api-skeleton/api" + "git.devices.local/mawas/golang-api-skeleton/lib/config" + "github.com/gin-gonic/gin" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func Command(appName string, cfgFile string, flagCfg map[string]interface{}) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: "Starts application", + Long: "Starts application", + // SilenceUsage: true, + // SilenceErrors: true, + DisableAutoGenTag: true, + RunE: func(cmd *cobra.Command, args []string) error { + return startServer(appName, cfgFile, flagCfg) + }, + } +} + +func startServer(appName string, cfgFile string, flagCfg map[string]interface{}) error { + if err := config.Initialize(appName, cfgFile, flagCfg); err != nil { + return fmt.Errorf("config:" + err.Error()) + } + // db, err := database.Connect(database.Credentials{ + // Host: viper.GetString("database.host"), + // Port: viper.GetInt("database.port"), + // Dialect: viper.GetString("database.dialect"), + // Database: viper.GetString("database.database"), + // User: viper.GetString("database.user"), + // Password: viper.GetString("database.password"), + // MaxOpenConn: viper.GetInt("database.port"), + // MaxIdleConn: viper.GetInt("database.port"), + // MaxLifeTime: viper.GetInt("database.port"), + // Debug: true, + // }) + // if err != nil { + // return fmt.Errorf("database:%v", err) + // } + app := gin.New() + app.Use(gin.Recovery()) + // app.Use(inject(db)) + api.ApplyRoutes(app) + if err := app.Run(fmt.Sprintf("%s:%s", viper.GetString("application.listenaddress"), viper.GetString("application.port"))); err != nil { + return fmt.Errorf("api:%v", err) + } + return nil +} + +// func ping(c *gin.Context) { +// c.JSON(200, gin.H{ +// "message": "pong", +// }) +// } + +// Inject injects database to gin context +// func inject(db *gorm.DB) gin.HandlerFunc { +// return func(c *gin.Context) { +// c.Set("db", db) +// c.Next() +// } +// } diff --git a/go.mod b/go.mod index 2e9ae8e..3a3de7b 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/dgraph-io/badger/v2 v2.2007.2 github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gin-gonic/gin v1.7.1 // indirect github.com/go-ozzo/ozzo-validation v3.6.0+incompatible github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/magiconair/properties v1.8.4 // indirect @@ -21,6 +22,8 @@ require ( github.com/spf13/viper v1.7.1 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/text v0.3.5 // indirect + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/go-playground/validator.v8 v8.18.2 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gorm.io/driver/mysql v1.0.3 diff --git a/go.sum b/go.sum index c620e86..669c69e 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= +github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -75,6 +79,13 @@ github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/ github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -93,12 +104,15 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -192,6 +206,8 @@ github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -208,6 +224,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -226,6 +244,7 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= @@ -241,7 +260,11 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= @@ -326,7 +349,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= @@ -483,6 +510,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= diff --git a/lib/common/common.go b/lib/common/common.go index ea02a61..78a7410 100644 --- a/lib/common/common.go +++ b/lib/common/common.go @@ -1,13 +1,16 @@ package common import ( - "crypto/rand" "time" - "github.com/oklog/ulid" "gorm.io/gorm" ) +const ( + CLIUserID = "01F5D7K0754ZDRPSKYDHE5CE0H" + CLIUsername = "cli" +) + type BasicFields struct { CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` CreatedByDB GUID `gorm:"column:created_by" json:"-"` @@ -22,7 +25,11 @@ type BasicFields struct { func (basicFields *BasicFields) BeforeCreate(tx *gorm.DB) error { if userID, ok := tx.Get("userID"); ok { - if guid, ok := userID.(GUID); ok { + if guidString, ok := userID.(string); ok { + guid, err := StringToGUID(guidString) + if err != nil { + return err + } basicFields.CreatedByDB = guid basicFields.UpdatedByDB = guid } @@ -51,10 +58,17 @@ type ModelGUIDPK struct { } func (guidPK *ModelGUIDPK) BeforeCreate(tx *gorm.DB) error { - id, err := ulid.New(ulid.Now(), rand.Reader) + id, err := NewGUID() + if err != nil { + return err + } guidPK.ID = GUID(id) if userID, ok := tx.Get("userID"); ok { - if guid, ok := userID.(GUID); ok { + if guidString, ok := userID.(string); ok { + guid, err := StringToGUID(guidString) + if err != nil { + return err + } guidPK.CreatedByDB = guid guidPK.UpdatedByDB = guid } @@ -68,10 +82,17 @@ type ModelHiddenGUIDPK struct { } func (guidPK *ModelHiddenGUIDPK) BeforeCreate(tx *gorm.DB) error { - id, err := ulid.New(ulid.Now(), rand.Reader) + id, err := NewGUID() + if err != nil { + return err + } guidPK.ID = GUID(id) if userID, ok := tx.Get("userID"); ok { - if guid, ok := userID.(GUID); ok { + if guidString, ok := userID.(string); ok { + guid, err := StringToGUID(guidString) + if err != nil { + return err + } guidPK.CreatedByDB = guid guidPK.UpdatedByDB = guid } diff --git a/lib/common/guid.go b/lib/common/guid.go index fb8e70b..e1e8286 100644 --- a/lib/common/guid.go +++ b/lib/common/guid.go @@ -1,6 +1,7 @@ package common import ( + "crypto/rand" "database/sql/driver" "github.com/oklog/ulid/v2" @@ -9,6 +10,11 @@ import ( // GUID our new ULID based datatype type GUID ulid.ULID +func NewGUID() (GUID, error) { + id, err := ulid.New(ulid.Now(), rand.Reader) + return GUID(id), err +} + // StringToGUID -> parse string to ULID GUID func StringToGUID(s string) (GUID, error) { id, err := ulid.Parse(s) // maybe ParseStrict diff --git a/main.go b/main.go index 1c9f6a7..c06c981 100644 --- a/main.go +++ b/main.go @@ -3,33 +3,26 @@ package main import ( "os" "strings" - "time" "git.devices.local/mawas/golang-api-skeleton/cmd" - "git.devices.local/mawas/golang-api-skeleton/lib/cache" - "git.devices.local/mawas/golang-api-skeleton/lib/common" - "git.devices.local/mawas/golang-api-skeleton/lib/config" - "git.devices.local/mawas/golang-api-skeleton/lib/database" - "git.devices.local/mawas/golang-api-skeleton/lib/utils" - "git.devices.local/mawas/golang-api-skeleton/models" - "git.devices.local/mawas/golang-api-skeleton/repositories" - "git.devices.local/mawas/golang-api-skeleton/services" log "github.com/sirupsen/logrus" - "github.com/spf13/viper" ) +const defaultHost = "unknown" + // https://towardsdatascience.com/building-restful-apis-in-golang-e3fe6e3f8f95 // https://www.voile.com/voile-straps.html // FIXME how to set unique index for username to make soft deletes possible but disallow duplicates... also how to cache a username which got delete and recerated... user ID would get overwritten, maybe pre/suffix deleted username? +// TODO add command for db migrate? no auto migrate? // TODO use badgerdb for permissions? path:[token1, token2...] // TODO add database loadbalancing feature... so allow more hosts use first as default and other ones as fallback -// TODO usrid and username for cli user is hardcoded.. no need to live in database // TODO get-stats cli command... which means unix socket support to communicate with server // TODO data versioning which will be used as reference from the log, also with predictive analytics for restore which means versioning of reference objects needs to be considered // TODO request body store as reference for log -// TODO because their is a badgerdb cache which tokens are existing or which userids are existing make an existence check at uuid generation just do be sure - +// TODO because there is a badgerdb cache which tokens are existing or which userids are existing make an existence check at uuid generation just do be sure +// TODO add logger to gin +// TODO make explicit getDBConnection function var ( Version string // allows to set version on build APPName string // allows to overwrite app name on build @@ -41,7 +34,7 @@ func main() { log.SetOutput(os.Stdout) hostname, err := os.Hostname() if err != nil { - hostname = "unknown" + hostname = defaultHost } contextLogger := log.WithFields(log.Fields{ "host": hostname, @@ -49,172 +42,204 @@ func main() { "app": APPName, }) // cobra sets env if flag is given so that viper can pick it up - cfgFile, flagCfg, err := cmd.Initialize(Version, APPName, APPDescription) - if err != nil { - os.Exit(1) - } // config priority: flags > env > config file - if err := config.Initialize(APPName, cfgFile, flagCfg); err != nil { - contextLogger.WithFields(log.Fields{ - "module": "config", - }).Fatal(err) - } - - // bootstrap cache - c, err := cache.Bootstrap() - if err != nil { - contextLogger.WithFields(log.Fields{ - "module": "cache", - }).Fatal(err) - } - defer c.Close() - db, err := database.Connect(database.Credentials{ - Host: viper.GetString("database.host"), - Port: viper.GetInt("database.port"), - Dialect: viper.GetString("database.dialect"), - Database: viper.GetString("database.database"), - User: viper.GetString("database.user"), - Password: viper.GetString("database.password"), - MaxOpenConn: viper.GetInt("database.port"), - MaxIdleConn: viper.GetInt("database.port"), - MaxLifeTime: viper.GetInt("database.port"), - Debug: true, - }) - if err != nil { - contextLogger.WithFields(log.Fields{ - "module": "database", - }).Fatal(err) - } - - db.AutoMigrate(&models.User{}) - db.AutoMigrate(&models.Token{}) - userRepo := repositories.NewUserRepository(db, common.GUID{}, "", nil) - userService := services.NewUserService(userRepo) + command := cmd.NewRootCmd(Version, APPName, APPDescription) - // Prefill cache - // TODO also add deleted users to cache for old references - if userData, err := userService.ReadAll(); err != nil { - contextLogger.WithFields(log.Fields{ - "module": "userService", - }).Fatal(err) - } else { - for i := range userData { - if err := c.Set("user:"+userData[i].ID.String(), userData[i].Username); err != nil { // userID -> username mapping - contextLogger.WithFields(log.Fields{ - "module": "cache", - }).Fatal(err) - } - if err := c.Set("user:"+userData[i].Username, userData[i].ID.String()); err != nil { // username -> userID mapping - contextLogger.WithFields(log.Fields{ - "module": "cache", - }).Fatal(err) - } - } - } - - var userID common.GUID - firstname := strings.ToLower(utils.RandString(utils.RandInt(5, 10))) - lastname := strings.ToLower(utils.RandString(utils.RandInt(6, 14))) - username := string([]byte(firstname)[0]) + lastname - user1 := &models.User{ - Username: username, - Firstname: strings.ToUpper(string([]byte(firstname)[0])) + string([]byte(firstname)[1:]), - Lastname: strings.ToUpper(string([]byte(lastname)[0])) + string([]byte(lastname)[1:]), - Email: username + "@acme.inc", - } - if result, err := userService.Create(user1); err != nil { - panic(err) - } else { - utils.PrettyPrintJSON(result) - if err := c.Set("user:"+result.ID.String(), result.Username); err != nil { // userID -> username mapping - contextLogger.WithFields(log.Fields{ - "module": "cache", - }).Fatal(err) - } - if err := c.Set("user:"+result.Username, result.ID.String()); err != nil { // username -> userID mapping + if err := command.Execute(); err != nil { + msg := strings.SplitN(err.Error(), ":", 1) + if len(msg) == 2 { contextLogger.WithFields(log.Fields{ - "module": "cache", - }).Fatal(err) + "module": msg[0], + }).Fatal(msg[1]) } - userID = result.ID } - - userRepo = repositories.NewUserRepository(db, userID, username, c) - userService = services.NewUserService(userRepo) - // userID db context? - // k := common.UsernameContextKey("username") - // ctx := context.WithValue(context.Background(), "username", username) - // db = db.WithContext(ctx) - // db = db.Set("username", username) - // fmt.Println(db.Get("username")) - - // create example token - tokenRepo := repositories.NewTokenRepository(db, userID, username, c) - tokenService := services.NewTokenService(tokenRepo) - token1 := &models.Token{ - ExpiresAt: time.Now().Add(24 * time.Hour), - UserID: userID, - Active: true, - } - if result1, err := tokenService.Create(token1); err != nil { - panic(err) - } else { - utils.PrettyPrintJSON(result1) - } - - result1, err := tokenService.ReadAll() - if err != nil { - panic(err) - } - utils.PrettyPrintJSON(result1) - - result, err := userService.ReadAll() - if err != nil { - panic(err) - } - utils.PrettyPrintJSON(result) - - firstname2 := strings.ToLower(utils.RandString(utils.RandInt(5, 10))) - lastname2 := strings.ToLower(utils.RandString(utils.RandInt(6, 14))) - username2 := string([]byte(firstname2)[0]) + lastname2 - user2 := &models.User{ - Username: username2, - Firstname: strings.ToUpper(string([]byte(firstname2)[0])) + string([]byte(firstname2)[1:]), - Lastname: strings.ToUpper(string([]byte(lastname2)[0])) + string([]byte(lastname2)[1:]), - Email: username2 + "@acme.inc", - } - if result, err := userService.Create(user2); err != nil { - panic(err) - } else { - utils.PrettyPrintJSON(result) - result3, err := userService.ReadByID(result.Username) - if err != nil { - panic(err) - } - utils.PrettyPrintJSON(result3) - } - - // db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) - // userRepo := user.NewRepository(db) - // userService := user.NewService(userRepo) - // user1 := &user.User{ - // Username: "jdoe", - // Firstname: "Joe", - // Lastname: "Doe", - // Email: "jdoe@acme.inc", - // } - // result, err := userService.Create(user1) + // asd := flagCfg["application.environment"].(*string) + // fmt.Printf("SUPI %s\n", *asd) + // cfgFile, flagCfg, err := cmd.Initialize(Version, APPName, APPDescription) // if err != nil { - // panic(err) + // os.Exit(1) // } - // fmt.Println(result) - - // fmt.Println("main.go --> Environment:", viper.GetString("application.environment")) - // fmt.Println("main.go --> Listen:", viper.GetString("application.listenaddress")) - // fmt.Println("main.go --> Database Port: ", viper.GetInt("database.port")) - // fmt.Println("main.go --> Application Port:", viper.GetInt("application.port")) - // contextLogger.WithFields(log.Fields{ - // "module": "config", - // }).Fatal("ADSADSASd") + // config priority: flags > env > config file + // rootCMD.Execute() + // asd := flagCfg["application.environment"].(*string) + // fmt.Printf("SUPI1 %s\n", *asd) + // fmt.Println("SUPI2", cfgFile, flagCfg) + + // if err := config.Initialize(APPName, cfgFile, flagCfg); err != nil { + // contextLogger.WithFields(log.Fields{ + // "module": "config", + // }).Fatal(err) + // } + // + // // bootstrap cache + // c, err := cache.Bootstrap() + // if err != nil { + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // defer c.Close() + // db, err := database.Connect(database.Credentials{ + // Host: viper.GetString("database.host"), + // Port: viper.GetInt("database.port"), + // Dialect: viper.GetString("database.dialect"), + // Database: viper.GetString("database.database"), + // User: viper.GetString("database.user"), + // Password: viper.GetString("database.password"), + // MaxOpenConn: viper.GetInt("database.port"), + // MaxIdleConn: viper.GetInt("database.port"), + // MaxLifeTime: viper.GetInt("database.port"), + // Debug: true, + // }) + // if err != nil { + // contextLogger.WithFields(log.Fields{ + // "module": "database", + // }).Fatal(err) + // } + // + // // add cli user to cache + // if err := c.Set("user:"+CLIUserID, CLIUsername); err != nil { // userID -> username mapping + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // if err := c.Set("user:"+CLIUsername, CLIUserID); err != nil { // username -> userID mapping + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // + // db.AutoMigrate(&models.User{}) + // db.AutoMigrate(&models.Token{}) + // userRepo := repositories.NewUserRepository(db, CLIUserID, CLIUsername, nil) + // userService := services.NewUserService(userRepo) + // + // // Prefill cache + // // TODO also add deleted users to cache for old references + // if userData, err := userService.ReadAll(); err != nil { + // contextLogger.WithFields(log.Fields{ + // "module": "userService", + // }).Fatal(err) + // } else { + // + // for i := range userData { + // if err := c.Set("user:"+userData[i].ID.String(), userData[i].Username); err != nil { // userID -> username mapping + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // if err := c.Set("user:"+userData[i].Username, userData[i].ID.String()); err != nil { // username -> userID mapping + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // } + // } + // + // var userID common.GUID + // firstname := strings.ToLower(utils.RandString(utils.RandInt(5, 10))) + // lastname := strings.ToLower(utils.RandString(utils.RandInt(6, 14))) + // username := string([]byte(firstname)[0]) + lastname + // user1 := &models.User{ + // Username: username, + // Firstname: strings.ToUpper(string([]byte(firstname)[0])) + string([]byte(firstname)[1:]), + // Lastname: strings.ToUpper(string([]byte(lastname)[0])) + string([]byte(lastname)[1:]), + // Email: username + "@acme.inc", + // } + // if result, err := userService.Create(user1); err != nil { + // panic(err) + // } else { + // utils.PrettyPrintJSON(result) + // if err := c.Set("user:"+result.ID.String(), result.Username); err != nil { // userID -> username mapping + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // if err := c.Set("user:"+result.Username, result.ID.String()); err != nil { // username -> userID mapping + // contextLogger.WithFields(log.Fields{ + // "module": "cache", + // }).Fatal(err) + // } + // userID = result.ID + // } + // + // userRepo = repositories.NewUserRepository(db, userID.String(), username, c) + // userService = services.NewUserService(userRepo) + // // userID db context? + // // k := common.UsernameContextKey("username") + // // ctx := context.WithValue(context.Background(), "username", username) + // // db = db.WithContext(ctx) + // // db = db.Set("username", username) + // // fmt.Println(db.Get("username")) + // + // // create example token + // tokenRepo := repositories.NewTokenRepository(db, userID.String(), username, c) + // tokenService := services.NewTokenService(tokenRepo) + // token1 := &models.Token{ + // ExpiresAt: time.Now().Add(24 * time.Hour), + // UserID: userID, + // Active: true, + // } + // if result1, err := tokenService.Create(token1); err != nil { + // panic(err) + // } else { + // utils.PrettyPrintJSON(result1) + // } + // + // result1, err := tokenService.ReadAll() + // if err != nil { + // panic(err) + // } + // utils.PrettyPrintJSON(result1) + // + // result, err := userService.ReadAll() + // if err != nil { + // panic(err) + // } + // utils.PrettyPrintJSON(result) + // + // firstname2 := strings.ToLower(utils.RandString(utils.RandInt(5, 10))) + // lastname2 := strings.ToLower(utils.RandString(utils.RandInt(6, 14))) + // username2 := string([]byte(firstname2)[0]) + lastname2 + // user2 := &models.User{ + // Username: username2, + // Firstname: strings.ToUpper(string([]byte(firstname2)[0])) + string([]byte(firstname2)[1:]), + // Lastname: strings.ToUpper(string([]byte(lastname2)[0])) + string([]byte(lastname2)[1:]), + // Email: username2 + "@acme.inc", + // } + // if result, err := userService.Create(user2); err != nil { + // panic(err) + // } else { + // utils.PrettyPrintJSON(result) + // result3, err := userService.ReadByID(result.Username) + // if err != nil { + // panic(err) + // } + // utils.PrettyPrintJSON(result3) + // } + // + // // db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{}) + // // userRepo := user.NewRepository(db) + // // userService := user.NewService(userRepo) + // // user1 := &user.User{ + // // Username: "jdoe", + // // Firstname: "Joe", + // // Lastname: "Doe", + // // Email: "jdoe@acme.inc", + // // } + // // result, err := userService.Create(user1) + // // if err != nil { + // // panic(err) + // // } + // // fmt.Println(result) + // + // // fmt.Println("main.go --> Environment:", viper.GetString("application.environment")) + // // fmt.Println("main.go --> Listen:", viper.GetString("application.listenaddress")) + // // fmt.Println("main.go --> Database Port: ", viper.GetInt("database.port")) + // // fmt.Println("main.go --> Application Port:", viper.GetInt("application.port")) + // // contextLogger.WithFields(log.Fields{ + // // "module": "config", + // // }).Fatal("ADSADSASd") + // } diff --git a/models/token.go b/models/token.go index 32ee467..085322a 100644 --- a/models/token.go +++ b/models/token.go @@ -1,13 +1,11 @@ package models import ( - "crypto/rand" "database/sql" "time" "git.devices.local/mawas/golang-api-skeleton/lib/cache" "git.devices.local/mawas/golang-api-skeleton/lib/common" - "github.com/oklog/ulid" "gorm.io/gorm" ) @@ -19,15 +17,22 @@ type Token struct { LastUsedJSON *time.Time `gorm:"-" json:"last_used,omitempty"` ExpiresAt time.Time `json:"expires_at"` UserID common.GUID `json:"asd"` - Username string `json:"username"` + Username string `gorm:"-" json:"username"` Active bool `json:"active"` } func (token *Token) BeforeCreate(tx *gorm.DB) error { - id, err := ulid.New(ulid.Now(), rand.Reader) - token.Token = common.GUID(id) + id, err := common.NewGUID() + if err != nil { + return err + } + token.Token = id if userID, ok := tx.Get("userID"); ok { - if guid, ok := userID.(common.GUID); ok { + if guidString, ok := userID.(string); ok { + guid, err := common.StringToGUID(guidString) + if err != nil { + return err + } token.CreatedByDB = guid token.UpdatedByDB = guid } diff --git a/repositories/repsitory.go b/repositories/repsitory.go index a004e53..a80e7f4 100644 --- a/repositories/repsitory.go +++ b/repositories/repsitory.go @@ -1,12 +1,11 @@ package repositories import ( - "git.devices.local/mawas/golang-api-skeleton/lib/common" "gorm.io/gorm" ) type repo struct { db *gorm.DB - userID common.GUID + userID string username string } diff --git a/repositories/token.go b/repositories/token.go index 9c71880..fd27541 100644 --- a/repositories/token.go +++ b/repositories/token.go @@ -3,7 +3,6 @@ package repositories import ( "git.devices.local/mawas/golang-api-skeleton/lib/cache" - "git.devices.local/mawas/golang-api-skeleton/lib/common" model "git.devices.local/mawas/golang-api-skeleton/models" "gorm.io/gorm" ) @@ -13,7 +12,7 @@ type tokenDAO struct { } // NewTokenRepository create new repository for model token -func NewTokenRepository(db *gorm.DB, userID common.GUID, username string, cache cache.Cache) *tokenDAO { +func NewTokenRepository(db *gorm.DB, userID string, username string, cache cache.Cache) *tokenDAO { db = db.Set("username", username).Set("userID", userID).Set("cache", cache) return &tokenDAO{ repo: repo{ diff --git a/repositories/user.go b/repositories/user.go index 4045872..8e14218 100644 --- a/repositories/user.go +++ b/repositories/user.go @@ -3,7 +3,6 @@ package repositories import ( "git.devices.local/mawas/golang-api-skeleton/lib/cache" - "git.devices.local/mawas/golang-api-skeleton/lib/common" model "git.devices.local/mawas/golang-api-skeleton/models" "gorm.io/gorm" ) @@ -13,7 +12,7 @@ type UserDAO struct { } // NewUserDAO create new repository for model user -func NewUserRepository(db *gorm.DB, userID common.GUID, username string, cache cache.Cache) *UserDAO { +func NewUserRepository(db *gorm.DB, userID string, username string, cache cache.Cache) *UserDAO { db = db.Set("username", username).Set("userID", userID).Set("cache", cache) return &UserDAO{ repo: repo{ diff --git a/services/user.go b/services/user.go index 0f7007d..fcb15bf 100644 --- a/services/user.go +++ b/services/user.go @@ -33,3 +33,14 @@ func (s *UserService) ReadAll() ([]*model.User, error) { func (s *UserService) Create(user *model.User) (*model.User, error) { return s.repo.Create(user) } + +// NewUser returns a new created user object +func NewUser(username, firstname, lastname, email string) model.User { + return model.User{ + Username: username, + Firstname: firstname, + Lastname: lastname, + Email: email, + PasswordHash: "generate_password_here", + } +}