diff --git a/server/api/api.go b/server/api/api.go index 2037cc0..222def5 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -1,13 +1,13 @@ package api import ( - "git.ctrlz.es/mgdelacroix/craban/services/store" + "git.ctrlz.es/mgdelacroix/craban/app" ) type API struct { - store *store.Store + App *app.App } -func NewAPI(store *store.Store) (*API, error) { - return &API{store: store}, nil +func NewAPI(app *app.App) *API { + return &API{App: app} } diff --git a/server/app/app.go b/server/app/app.go new file mode 100644 index 0000000..bbb481a --- /dev/null +++ b/server/app/app.go @@ -0,0 +1,18 @@ +package app + +import ( + "git.ctrlz.es/mgdelacroix/craban/model" + "git.ctrlz.es/mgdelacroix/craban/services/store" +) + +type App struct { + config *model.Config + store *store.Store +} + +func NewApp(config *model.Config, store *store.Store) *App { + return &App{ + config: config, + store: store, + } +} diff --git a/server/app/user.go b/server/app/user.go new file mode 100644 index 0000000..17a8496 --- /dev/null +++ b/server/app/user.go @@ -0,0 +1,36 @@ +package app + +import ( + "fmt" + + "git.ctrlz.es/mgdelacroix/craban/model" + "git.ctrlz.es/mgdelacroix/craban/utils" +) + +func (a *App) CreateUser(username, password, name, mail string) (*model.User, error) { + hashedPassword, err := utils.Encrypt(password) + if err != nil { + return nil, fmt.Errorf("cannot create user: %w", err) + } + + newUser := &model.User{ + Username: username, + Password: hashedPassword, + Name: name, + Mail: mail, + } + + if err := newUser.IsValid(); err != nil { + return nil, fmt.Errorf("invalid user for creation: %w", err) + } + + return a.store.User().Create(newUser) +} + +func (a *App) ListUsers() ([]*model.User, error) { + return a.store.User().List() +} + +func (a *App) DeleteUserByUsername(username string) error { + return a.store.User().DeleteByUsername(username) +} diff --git a/server/cmd/craban/commands/root.go b/server/cmd/craban/commands/root.go index 881f76e..77c0c2b 100644 --- a/server/cmd/craban/commands/root.go +++ b/server/cmd/craban/commands/root.go @@ -16,6 +16,7 @@ func RootCmd() *cobra.Command { cmd.AddCommand( ServeCmd(), + UserCmd(), ) return cmd diff --git a/server/cmd/craban/commands/serve.go b/server/cmd/craban/commands/serve.go index b7c23be..72d7982 100644 --- a/server/cmd/craban/commands/serve.go +++ b/server/cmd/craban/commands/serve.go @@ -14,14 +14,15 @@ func ServeCmd() *cobra.Command { return &cobra.Command{ Use: "serve", Short: "Starts the craban server", + Args: cobra.NoArgs, Run: serveCmdF, } } func serveCmdF(cmd *cobra.Command, _ []string) { - configPath, _ := cmd.Flags().GetString("config") + config, _ := cmd.Flags().GetString("config") - srv, err := server.NewServerWithConfigPath(configPath) + srv, err := server.NewServerWithConfigPath(config) if err != nil { log.Error().Err(err).Msg("cannot create server") os.Exit(1) diff --git a/server/cmd/craban/commands/user.go b/server/cmd/craban/commands/user.go new file mode 100644 index 0000000..66ef84c --- /dev/null +++ b/server/cmd/craban/commands/user.go @@ -0,0 +1,123 @@ +package commands + +import ( + "os" + + "git.ctrlz.es/mgdelacroix/craban/server" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +func UserCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "user", + Short: "User related commands", + } + + cmd.AddCommand( + CreateUserCmd(), + ListUserCmd(), + DeleteUserCmd(), + ) + + return cmd +} + +func CreateUserCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "Create a new user", + Args: cobra.NoArgs, + Run: createUserCmdF, + } + + cmd.Flags().StringP("username", "u", "", "the username of the new user") + cmd.MarkFlagRequired("username") + cmd.Flags().StringP("password", "p", "", "the password of the new user") + cmd.MarkFlagRequired("password") + cmd.Flags().StringP("name", "n", "", "the name of the new user") + cmd.MarkFlagRequired("name") + cmd.Flags().StringP("mail", "m", "", "the mail of the new user") + cmd.MarkFlagRequired("mail") + + return cmd +} + +func ListUserCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List the users in the system", + Args: cobra.NoArgs, + Run: listUserCmdF, + } +} + +func DeleteUserCmd() *cobra.Command { + return &cobra.Command{ + Use: "delete ", + Short: "Delete a user by username", + Args: cobra.ExactArgs(1), + Run: deleteUserCmdF, + } +} + +func createUserCmdF(cmd *cobra.Command, _ []string) { + username, _ := cmd.Flags().GetString("username") + password, _ := cmd.Flags().GetString("password") + name, _ := cmd.Flags().GetString("name") + mail, _ := cmd.Flags().GetString("mail") + + config, _ := cmd.Flags().GetString("config") + srv, err := server.NewServerWithConfigPath(config) + if err != nil { + log.Error().Err(err).Msg("cannot create server") + os.Exit(1) + } + defer srv.Store.Close() + + user, err := srv.App.CreateUser(username, password, name, mail) + if err != nil { + log.Error().Err(err).Msg("user couldn't be created") + os.Exit(1) + } + + log.Info().Str("username", user.Username).Msg("user successfully created") +} + +func listUserCmdF(cmd *cobra.Command, _ []string) { + config, _ := cmd.Flags().GetString("config") + srv, err := server.NewServerWithConfigPath(config) + if err != nil { + log.Error().Err(err).Msg("cannot create server") + os.Exit(1) + } + defer srv.Store.Close() + + users, err := srv.App.ListUsers() + if err != nil { + log.Error().Err(err).Msg("cannot get user list") + os.Exit(1) + } + + for _, user := range users { + log.Info().Str("username", user.Username).Msg("") + } +} + +func deleteUserCmdF(cmd *cobra.Command, args []string) { + config, _ := cmd.Flags().GetString("config") + srv, err := server.NewServerWithConfigPath(config) + if err != nil { + log.Error().Err(err).Msg("cannot create server") + os.Exit(1) + } + defer srv.Store.Close() + + if err := srv.App.DeleteUserByUsername(args[0]); err != nil { + log.Error().Err(err).Str("username", args[0]).Msg("cannot delete user") + os.Exit(1) + } + + log.Info().Str("username", args[0]).Msg("user successfully deleted") +} diff --git a/server/server/server.go b/server/server/server.go index e6adace..47ff661 100644 --- a/server/server/server.go +++ b/server/server/server.go @@ -6,6 +6,7 @@ import ( "time" "git.ctrlz.es/mgdelacroix/craban/api" + "git.ctrlz.es/mgdelacroix/craban/app" "git.ctrlz.es/mgdelacroix/craban/model" "git.ctrlz.es/mgdelacroix/craban/services/store" "git.ctrlz.es/mgdelacroix/craban/web" @@ -22,6 +23,8 @@ func init() { type Server struct { Config *model.Config + App *app.App + API *api.API Store *store.Store WebServer *web.WebServer } @@ -43,17 +46,18 @@ func NewServer(config *model.Config) (*Server, error) { } log.Debug().Msg("store created") - webAPI, err := api.NewAPI(store) - if err != nil { - return nil, fmt.Errorf("cannot create API: %w", err) - } + app := app.NewApp(config, store) + log.Debug().Msg("app created") + api := api.NewAPI(app) log.Debug().Msg("API created") - webserver.RegisterRoutes(webAPI) + webserver.RegisterRoutes(api) log.Debug().Msg("webserver routes registered with the API") return &Server{ Config: config, WebServer: webserver, + App: app, + API: api, Store: store, }, err } diff --git a/server/services/store/user.go b/server/services/store/user.go index 91c2a54..d9d594c 100644 --- a/server/services/store/user.go +++ b/server/services/store/user.go @@ -85,3 +85,32 @@ func (us *UserStore) Create(user *model.User) (*model.User, error) { return us.GetByID(int(id)) } + +// ToDo: add pagination and filtering +func (us *UserStore) List() ([]*model.User, error) { + rows, err := us.Q().Select(userColumns...). + From("users"). + Query() + if err != nil { + return nil, err + } + defer rows.Close() + + users, err := us.usersFromRows(rows) + if err != nil { + return nil, err + } + + if len(users) == 0 { + return nil, sql.ErrNoRows + } + + return users, nil +} + +func (us *UserStore) DeleteByUsername(username string) error { + _, err := us.Q().Delete("users"). + Where(sq.Eq{"username": username}). + Exec() + return err +}