From 8d52e408a6c38cf1a38c5bdadcfd149fd756e0f9 Mon Sep 17 00:00:00 2001 From: louis Date: Mon, 23 Oct 2023 16:55:22 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Add=20C?= =?UTF-8?q?ommand=20to=20create=20User?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/db.go | 14 +-------- cmd/root.go | 15 ++++++++++ cmd/run.go | 41 -------------------------- cmd/user.go | 61 +++++++++++++++++++++++++++++++++++++++ config.yml.dist | 2 -- docs/configuration.md | 2 -- docs/index.md | 4 +++ internal/config/config.go | 3 -- internal/storage/user.go | 2 +- 9 files changed, 82 insertions(+), 62 deletions(-) create mode 100644 cmd/user.go diff --git a/cmd/db.go b/cmd/db.go index d3c76045..275d15a4 100644 --- a/cmd/db.go +++ b/cmd/db.go @@ -4,7 +4,6 @@ import ( "github.com/asdine/storm" "github.com/spf13/cobra" "github.com/systemli/ticker/internal/legacy" - "github.com/systemli/ticker/internal/storage" ) var ( @@ -27,19 +26,8 @@ var ( } defer oldDb.Close() - db, err := storage.OpenGormDB(cfg.Database.Type, cfg.Database.DSN, log) - if err != nil { - log.WithError(err).Fatal("Unable to open the new database") - } - - if err := storage.MigrateDB(db); err != nil { - log.WithError(err).Fatal("Unable to migrate the database") - } - legacyStorage := legacy.NewLegacyStorage(oldDb) - newStorage := storage.NewSqlStorage(db, cfg.UploadPath) - - migration := legacy.NewMigration(legacyStorage, newStorage) + migration := legacy.NewMigration(legacyStorage, store) if err := migration.Do(); err != nil { log.WithError(err).Fatal("Unable to migrate the database") } diff --git a/cmd/root.go b/cmd/root.go index e25d74eb..f1ad4a76 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,11 +9,15 @@ import ( "github.com/systemli/ticker/internal/bridge" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/logger" + "github.com/systemli/ticker/internal/storage" + "gorm.io/gorm" ) var ( configPath string cfg config.Config + db *gorm.DB + store *storage.SqlStorage log = logrus.New() @@ -42,11 +46,22 @@ func initConfig() { } log = logger.NewLogrus(cfg.LogLevel, cfg.LogFormat) + + var err error + db, err = storage.OpenGormDB(cfg.Database.Type, cfg.Database.DSN, log) + if err != nil { + log.WithError(err).Fatal("could not connect to database") + } + store = storage.NewSqlStorage(db, cfg.UploadPath) + if err := storage.MigrateDB(db); err != nil { + log.WithError(err).Fatal("could not migrate database") + } } func Execute() { rootCmd.AddCommand(runCmd) rootCmd.AddCommand(dbCmd) + rootCmd.AddCommand(userCmd) rootCmd.AddCommand(versionCmd) if err := rootCmd.Execute(); err != nil { diff --git a/cmd/run.go b/cmd/run.go index b4aa69b5..fab28b6c 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -8,11 +8,8 @@ import ( "time" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/sethvargo/go-password/password" "github.com/spf13/cobra" "github.com/systemli/ticker/internal/api" - "github.com/systemli/ticker/internal/config" - "github.com/systemli/ticker/internal/storage" ) var ( @@ -36,23 +33,12 @@ var ( log.Fatal(http.ListenAndServe(cfg.MetricsListen, nil)) }() - db, err := storage.OpenGormDB(cfg.Database.Type, cfg.Database.DSN, log) - if err != nil { - log.WithError(err).Fatal("could not connect to database") - } - store := storage.NewSqlStorage(db, cfg.UploadPath) - if err := storage.MigrateDB(db); err != nil { - log.WithError(err).Fatal("could not migrate database") - } - router := api.API(cfg, store, log) server := &http.Server{ Addr: cfg.Listen, Handler: router, } - firstRun(store, cfg) - go func() { if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) @@ -75,30 +61,3 @@ var ( }, } ) - -func firstRun(store storage.Storage, config config.Config) { - count, err := store.CountUser() - if err != nil { - log.Fatal("error using database") - } - - if count == 0 { - pw, err := password.Generate(24, 3, 3, false, false) - if err != nil { - log.Fatal(err) - } - - user, err := storage.NewUser(config.Initiator, pw) - user.IsSuperAdmin = true - if err != nil { - log.Fatal("could not create first user") - } - - err = store.SaveUser(&user) - if err != nil { - log.Fatal("could not persist first user") - } - - log.WithField("email", user.Email).WithField("password", pw).Info("admin user created (change password now!)") - } -} diff --git a/cmd/user.go b/cmd/user.go new file mode 100644 index 00000000..89016e62 --- /dev/null +++ b/cmd/user.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/systemli/ticker/internal/storage" + + pwd "github.com/sethvargo/go-password/password" +) + +var ( + email string + password string + isSuperAdmin bool + + userCmd = &cobra.Command{ + Use: "user", + Short: "Manage users", + Long: "Commands for managing users.", + Args: cobra.ExactArgs(1), + } + + userCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new user", + Run: func(cmd *cobra.Command, args []string) { + var err error + if email == "" { + log.Fatal("email is required") + } + if password == "" { + password, err = pwd.Generate(24, 3, 3, false, false) + if err != nil { + log.WithError(err).Fatal("could not generate password") + } + } + + user, err := storage.NewUser(email, password) + if err != nil { + log.WithError(err).Fatal("could not create user") + } + user.IsSuperAdmin = isSuperAdmin + + if err := store.SaveUser(&user); err != nil { + log.WithError(err).Fatal("could not save user") + } + + fmt.Printf("Created user %d\n", user.ID) + fmt.Printf("Password: %s\n", password) + }, + } +) + +func init() { + userCmd.AddCommand(userCreateCmd) + + userCreateCmd.Flags().StringVar(&email, "email", "", "email address of the user") + userCreateCmd.Flags().StringVar(&password, "password", "", "password of the user") + userCreateCmd.Flags().BoolVar(&isSuperAdmin, "super-admin", false, "make the user a super admin") +} diff --git a/config.yml.dist b/config.yml.dist index 0c8884f0..ea3aa26a 100644 --- a/config.yml.dist +++ b/config.yml.dist @@ -4,8 +4,6 @@ listen: "localhost:8080" log_level: "error" # log_format sets log format for logrus (default: json) log_format: "json" -# initiator is the email for the first admin user (see password in logs) -initiator: "admin@systemli.org" # configuration for the database database: type: "sqlite" # postgres, mysql, sqlite diff --git a/docs/configuration.md b/docs/configuration.md index 33c0f157..0ee7a51b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -7,8 +7,6 @@ listen: "localhost:8080" log_level: "error" # log_format sets log format for logrus (default: json) log_format: "json" -# initiator is the email for the first admin user (see password in logs) -initiator: "admin@systemli.org" # configuration for the database database: type: "sqlite" # postgres, mysql, sqlite diff --git a/docs/index.md b/docs/index.md index b0ade50d..c4d4a3b6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,10 @@ This repository contains the API for the [Systemli Ticker Project](https://www.s curl http://localhost:8080/healthz +4. Create a user + + go run . user create --email --password --super-admin + ## Testing ```shell diff --git a/internal/config/config.go b/internal/config/config.go index c522f244..88716857 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -15,7 +15,6 @@ type Config struct { Listen string `mapstructure:"listen"` LogLevel string `mapstructure:"log_level"` LogFormat string `mapstructure:"log_format"` - Initiator string `mapstructure:"initiator"` Secret string `mapstructure:"secret"` Database Database `mapstructure:"database"` TelegramBotToken string `mapstructure:"telegram_bot_token"` @@ -39,7 +38,6 @@ func NewConfig() Config { Listen: ":8080", LogLevel: "debug", LogFormat: "json", - Initiator: "admin@systemli.org", Secret: secret, Database: Database{Type: "sqlite", DSN: "ticker.db"}, MetricsListen: ":8181", @@ -63,7 +61,6 @@ func LoadConfig(path string) Config { viper.SetDefault("listen", c.Listen) viper.SetDefault("log_level", c.LogLevel) viper.SetDefault("log_format", c.LogFormat) - viper.SetDefault("initiator", c.Initiator) viper.SetDefault("secret", c.Secret) viper.SetDefault("database", c.Database) viper.SetDefault("metrics_listen", c.MetricsListen) diff --git a/internal/storage/user.go b/internal/storage/user.go index abb73fbf..836a2fc9 100644 --- a/internal/storage/user.go +++ b/internal/storage/user.go @@ -10,7 +10,7 @@ type User struct { ID int `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time - Email string `storm:"unique"` + Email string `gorm:"uniqueIndex;not null"` EncryptedPassword string IsSuperAdmin bool Tickers []Ticker `gorm:"many2many:ticker_users;"` From 4fe63ccaecf8e2e96f6ca2d3a2cb49d6db2c3cf4 Mon Sep 17 00:00:00 2001 From: louis Date: Mon, 23 Oct 2023 16:59:47 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=97=83=EF=B8=8F=20Remove=20CountUser?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/storage/mock_Storage.go | 24 ------------------------ internal/storage/sql_storage.go | 7 ------- internal/storage/sql_storage_test.go | 10 ---------- internal/storage/storage.go | 1 - 4 files changed, 42 deletions(-) diff --git a/internal/storage/mock_Storage.go b/internal/storage/mock_Storage.go index 810055c5..f137410c 100644 --- a/internal/storage/mock_Storage.go +++ b/internal/storage/mock_Storage.go @@ -27,30 +27,6 @@ func (_m *MockStorage) AddTickerUser(ticker *Ticker, user *User) error { return r0 } -// CountUser provides a mock function with given fields: -func (_m *MockStorage) CountUser() (int, error) { - ret := _m.Called() - - var r0 int - var r1 error - if rf, ok := ret.Get(0).(func() (int, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() int); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(int) - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // DeleteAttachmentsByMessage provides a mock function with given fields: message func (_m *MockStorage) DeleteAttachmentsByMessage(message Message) error { ret := _m.Called(message) diff --git a/internal/storage/sql_storage.go b/internal/storage/sql_storage.go index 8e303ca1..8edfafb5 100644 --- a/internal/storage/sql_storage.go +++ b/internal/storage/sql_storage.go @@ -21,13 +21,6 @@ func NewSqlStorage(db *gorm.DB, uploadPath string) *SqlStorage { } } -func (s *SqlStorage) CountUser() (int, error) { - var count int64 - err := s.DB.Model(&User{}).Count(&count).Error - - return int(count), err -} - func (s *SqlStorage) FindUsers() ([]User, error) { users := make([]User, 0) err := s.DB.Find(&users).Error diff --git a/internal/storage/sql_storage_test.go b/internal/storage/sql_storage_test.go index c069cfee..4fccc8e1 100644 --- a/internal/storage/sql_storage_test.go +++ b/internal/storage/sql_storage_test.go @@ -46,16 +46,6 @@ var _ = Describe("SqlStorage", func() { db.Exec("DELETE FROM uploads") }) - Describe("CountUser", func() { - It("returns the number of users", func() { - Expect(store.CountUser()).To(Equal(0)) - - err := db.Create(&User{}).Error - Expect(err).ToNot(HaveOccurred()) - Expect(store.CountUser()).To(Equal(1)) - }) - }) - Describe("FindUsers", func() { It("returns all users", func() { users, err := store.FindUsers() diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 5670562e..caadc56d 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -9,7 +9,6 @@ import ( var log = logrus.WithField("package", "storage") type Storage interface { - CountUser() (int, error) FindUsers() ([]User, error) FindUserByID(id int) (User, error) FindUsersByIDs(ids []int) ([]User, error)