diff --git a/cmd/pasty/command_cleanup.go b/cmd/pasty/command_cleanup.go new file mode 100644 index 0000000..16abc57 --- /dev/null +++ b/cmd/pasty/command_cleanup.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +func (router *consoleCommandRouter) Cleanup(args []string) { + if len(args) == 0 { + fmt.Println("Expected 1 argument.") + return + } + lifetime, err := time.ParseDuration(args[0]) + if err != nil { + fmt.Printf("Could not parse duration: %s.\n", err.Error()) + return + } + amount, err := router.Storage.Pastes().DeleteOlderThan(context.Background(), lifetime) + if err != nil { + if err != nil { + fmt.Printf("Could not delete pastes: %s.\n", err.Error()) + return + } + } + fmt.Printf("Deleted %d pastes older than %s.\n", amount, lifetime) +} diff --git a/cmd/pasty/command_delete.go b/cmd/pasty/command_delete.go new file mode 100644 index 0000000..2b92be4 --- /dev/null +++ b/cmd/pasty/command_delete.go @@ -0,0 +1,28 @@ +package main + +import ( + "context" + "fmt" +) + +func (router *consoleCommandRouter) Delete(args []string) { + if len(args) == 0 { + fmt.Println("Expected 1 argument.") + return + } + pasteID := args[0] + paste, err := router.Storage.Pastes().FindByID(context.Background(), pasteID) + if err != nil { + fmt.Printf("Could not look up paste: %s.\n", err.Error()) + return + } + if paste == nil { + fmt.Printf("Invalid paste ID: %s.\n", pasteID) + return + } + if err := router.Storage.Pastes().DeleteByID(context.Background(), pasteID); err != nil { + fmt.Printf("Could not delete paste: %s.\n", err.Error()) + return + } + fmt.Printf("Deleted paste %s.\n", pasteID) +} diff --git a/cmd/pasty/command_router.go b/cmd/pasty/command_router.go new file mode 100644 index 0000000..4f6bdc9 --- /dev/null +++ b/cmd/pasty/command_router.go @@ -0,0 +1,73 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/lus/pasty/internal/config" + "github.com/lus/pasty/internal/storage" + "github.com/rs/zerolog/log" + "os" + "regexp" + "strings" + "syscall" +) + +var whitespaceRegex = regexp.MustCompile("\\s+") + +type consoleCommandRouter struct { + Config *config.Config + Storage storage.Driver +} + +func (router *consoleCommandRouter) Listen() { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Print("> ") + input, err := reader.ReadString('\n') + if err != nil { + log.Err(err).Msg("Could not read console input.") + continue + } + + commandData := strings.Split(whitespaceRegex.ReplaceAllString(strings.TrimSpace(input), " "), " ") + if len(commandData) == 0 { + fmt.Println("Invalid command.") + continue + } + + handle := strings.ToLower(commandData[0]) + var args []string + if len(commandData) > 1 { + args = commandData[1:] + } + + switch handle { + case "help": + fmt.Println("Available commands:") + fmt.Println(" help : Shows this overview") + fmt.Println(" stop : Stops the application") + fmt.Println(" setmodtoken : Changes the modification token of the paste with ID to ") + fmt.Println(" delete : Deletes the paste with ID ") + fmt.Println(" cleanup : Deletes all pastes that are older than ") + break + case "stop": + if err := syscall.Kill(syscall.Getpid(), syscall.SIGINT); err != nil { + fmt.Printf("Could not send interrupt signal: %s.\nUse Ctrl+C instead.\n", err.Error()) + break + } + return + case "setmodtoken": + router.SetModificationToken(args) + break + case "delete": + router.Delete(args) + break + case "cleanup": + router.Cleanup(args) + break + default: + fmt.Println("Invalid command.") + break + } + } +} diff --git a/cmd/pasty/command_set_modification_token.go b/cmd/pasty/command_set_modification_token.go new file mode 100644 index 0000000..c99c2b0 --- /dev/null +++ b/cmd/pasty/command_set_modification_token.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "fmt" +) + +func (router *consoleCommandRouter) SetModificationToken(args []string) { + if len(args) < 2 { + fmt.Println("Expected 2 arguments.") + return + } + pasteID := args[0] + newToken := args[1] + paste, err := router.Storage.Pastes().FindByID(context.Background(), pasteID) + if err != nil { + fmt.Printf("Could not look up paste: %s.\n", err.Error()) + return + } + if paste == nil { + fmt.Printf("Invalid paste ID: %s.\n", pasteID) + return + } + paste.ModificationToken = newToken + if err := paste.HashModificationToken(); err != nil { + fmt.Printf("Could not hash modification token: %s.\n", err.Error()) + return + } + if err := router.Storage.Pastes().Upsert(context.Background(), paste); err != nil { + fmt.Printf("Could not update paste: %s.\n", err.Error()) + return + } + fmt.Printf("Changed modification token of paste %s to %s.\n", pasteID, newToken) +} diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 76b43d2..4ee6028 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -132,8 +132,16 @@ func main() { } }() - // Wait for an interrupt signal - log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") + if !cfg.ConsoleCommandsEnabled { + log.Info().Msg("The application has been started. Use Ctrl+C to shut it down.") + } else { + log.Info().Msg("The application has been started and listens to console commands. Use Ctrl+C or 'stop' to shut it down.") + go (&consoleCommandRouter{ + Config: cfg, + Storage: driver, + }).Listen() + } + shutdownChan := make(chan os.Signal, 1) signal.Notify(shutdownChan, os.Interrupt) <-shutdownChan diff --git a/internal/config/config.go b/internal/config/config.go index 3796860..e3b4b74 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,6 +18,7 @@ type Config struct { ModificationTokenCharset string `default:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" split_words:"true"` RateLimit string `default:"30-M" split_words:"true"` PasteLengthCap int `default:"50000" split_words:"true"` + ConsoleCommandsEnabled bool `default:"true" split_words:"true"` Cleanup *CleanupConfig Reports *ReportConfig Postgres *PostgresConfig