add more commands and stuff

This commit is contained in:
Gustavo Maronato 2023-09-06 13:00:56 -03:00
parent 970990123e
commit 1931e85213
Signed by: maronato
SSH Key Fingerprint: SHA256:2Gw7kwMz/As+2UkR1qQ/qYYhn+WNh3FGv6ozhoRrLcs
7 changed files with 156 additions and 23 deletions

View File

@ -8,8 +8,8 @@ linters:
- contextcheck
- cyclop
- decorder
- dogsled
- dupl
# - dogsled
# - dupl
- durationcheck
- errcheck
- errchkjson

View File

@ -11,8 +11,10 @@ install: install-frontend install-backend
frontend:
VITE_API_URL=/api npm run --prefix frontend build
VERSION=$(shell git describe --tags --abbrev=0)
backend:
CGO_ENABLED=0 go build -o goshort goshort.go
CGO_ENABLED=0 go build -v -ldflags="-X 'main.version=${VERSION}'" -o goshort goshort.go
all: frontend backend

View File

@ -3,6 +3,7 @@ package cmd
import (
"context"
"errors"
"flag"
"fmt"
"os"
@ -12,12 +13,28 @@ import (
devcmd "git.maronato.dev/maronato/goshort/cmd/dev"
healthcheckcmd "git.maronato.dev/maronato/goshort/cmd/healthcheck"
servecmd "git.maronato.dev/maronato/goshort/cmd/serve"
"git.maronato.dev/maronato/goshort/cmd/shared"
"git.maronato.dev/maronato/goshort/internal/config"
"git.maronato.dev/maronato/goshort/internal/util/logging"
"github.com/peterbourgon/ff/v3/ffcli"
)
func Run() {
var (
// ErrUnknownCommand is returned when the user tries to run an unknown command.
ErrUnknownCommand = errors.New("unknown command")
// ErrUnknownHelpTopic is returned when the user tries to run an unknown help topic.
ErrUnknownHelpTopic = errors.New("unknown help topic")
)
func NewUnknownCommand(cmd string) error {
return fmt.Errorf("%q: %w\nRun 'goshort help' for usage", cmd, ErrUnknownCommand)
}
func NewUnknownHelpTopic(topic string) error {
return fmt.Errorf("%q: %w. Run 'goshort help'", topic, ErrUnknownHelpTopic)
}
func Run(version string) error {
// Create the application-wide context, and
// implement graceful shutdown.
ctx, cancel := context.WithCancel(context.Background())
@ -26,11 +43,12 @@ func Run() {
trapSignalsCrossPlatform(cancel)
// Create the root command and register subcommands.
rootCmd, cfg := newRootCmd()
rootCmd.Subcommands = []*ffcli.Command{
rootCmd, cfg := newRootCmd(version)
rootCmd.Subcommands = append(
rootCmd.Subcommands,
servecmd.New(cfg),
healthcheckcmd.New(cfg),
}
)
// Look for the env ENV_DOCKER=true to disable the dev command
// since the docker image won't have node installed.
@ -40,14 +58,20 @@ func Run() {
// Parse the command-line arguments.
if err := rootCmd.Parse(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
if errors.Is(err, flag.ErrHelp) {
return nil
}
return fmt.Errorf("%w", err)
}
// Validate config
if err := config.Validate(cfg); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
if errors.Is(err, flag.ErrHelp) {
return nil
}
return fmt.Errorf("%w", err)
}
// Create system logger
@ -56,26 +80,80 @@ func Run() {
// Run the command.
if err := rootCmd.Run(ctx); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
if errors.Is(err, flag.ErrHelp) {
return nil
}
return fmt.Errorf("%w", err)
}
return nil
}
// newRootCmd constructs a root command from the provided options.
func newRootCmd() (*ffcli.Command, *config.Config) {
func newRootCmd(version string) (*ffcli.Command, *config.Config) {
var cfg config.Config
fs := flag.NewFlagSet("goshort", flag.ContinueOnError)
return &ffcli.Command{
cmd := &ffcli.Command{
Name: "goshort",
ShortUsage: "goshort <subcommand> [flags]",
ShortHelp: "goshort is a tiny URL shortener",
FlagSet: fs,
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
// If the user didn't provide any subcommand, then
// we'll just show the help message.
if len(args) == 0 {
return flag.ErrHelp
}
return NewUnknownCommand(args[0])
},
}, &cfg
}
cmd.Subcommands = []*ffcli.Command{
// Register help command
{
Name: "help",
ShortUsage: "help [command]",
ShortHelp: "Show help for a command",
Exec: func(ctx context.Context, args []string) error {
if len(args) == 0 || args[0] == "help" {
cmd.FlagSet.Usage()
fmt.Printf("\nUse 'goshort help <command>' for more information about that command.\n\n")
return nil
}
for _, c := range cmd.Subcommands {
if c.Name == args[0] {
return c.ParseAndRun(ctx, []string{"-h"}) //nolint:wrapcheck // We don't want to wrap the error here
}
}
return NewUnknownHelpTopic(args[0])
},
},
// Register version command
{
Name: "version",
ShortUsage: "version",
ShortHelp: "Show version information",
Exec: func(ctx context.Context, args []string) error {
fmt.Printf("goshort version %s\n", version)
return nil
},
},
// Register config command
NewConfigCmd(&cfg),
}
return cmd, &cfg
}
// https://github.com/caddyserver/caddy/blob/fbb0ecfa322aa7710a3448453fd3ae40f037b8d1/sigtrap.go#L37
@ -100,3 +178,26 @@ func trapSignalsCrossPlatform(cancel context.CancelFunc) {
}
}()
}
func NewConfigCmd(cfg *config.Config) *ffcli.Command {
// Create the flagset and register the flags.
fs := flag.NewFlagSet("goshort serve", flag.ContinueOnError)
shared.RegisterBaseFlags(fs, cfg)
shared.RegisterServerFlags(fs, cfg)
fs.BoolVar(&cfg.Prod, "prod", config.DefaultProd, "run in production mode")
// Return the ffcli command.
return &ffcli.Command{
Name: "config",
ShortUsage: "goshort config [flags]",
ShortHelp: "Prints the current config",
FlagSet: fs,
Exec: func(ctx context.Context, args []string) error {
cfg.PrettyPrint()
return nil
},
Options: shared.NewSharedCmdOptions(),
}
}

View File

@ -1,8 +1,20 @@
package main
import "git.maronato.dev/maronato/goshort/cmd"
import (
"fmt"
"os"
"git.maronato.dev/maronato/goshort/cmd"
)
// Version of the application
var version = "dev"
func main() {
// Run root command
cmd.Run()
if err := cmd.Run(version); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}

View File

@ -1,3 +1,4 @@
//nolint:forbidigo // Allow printing for prettyprint
package config
import (
@ -127,3 +128,16 @@ func Validate(cfg *Config) error {
return nil
}
func (c *Config) PrettyPrint() {
fmt.Println("Configuration:")
fmt.Println(" - Production mode:", c.Prod)
fmt.Println(" - Debug mode:", c.Debug)
fmt.Println(" - Host:", c.Host)
fmt.Println(" - Port:", c.Port)
fmt.Println(" - UI Port:", c.UIPort)
fmt.Println(" - Database type:", c.DBType)
fmt.Println(" - Database URL:", c.DBURL)
fmt.Println(" - Session duration:", c.SessionDuration)
fmt.Println(" - Disable registration:", c.DisableRegistration)
}

View File

@ -53,8 +53,8 @@ func setup(ctx context.Context, cfg *config.Config) (
*apiserver.APIHandler,
*shortservice.ShortService,
*userservice.UserService,
*tokenservice.TokenService,
*shortlogservice.ShortLogService,
*tokenservice.TokenService, //nolint:unparam // We need to return this for testing purposes
*shortlogservice.ShortLogService, //nolint:unparam // We need to return this for testing purposes
) {
str := newStorage(ctx, cfg)
@ -164,7 +164,7 @@ func TestEndpointCreateShort(t *testing.T) {
tests := []struct {
name string
ctx context.Context
ctx context.Context //nolint:containedctx // We need to pass the context to test user presence
body io.Reader
responseCode int
shortName string
@ -228,7 +228,10 @@ func TestEndpointCreateShort(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, req := makeRequestResponse(tt.ctx, "POST", "/api/shorts", tt.body)
ctx, cancel := context.WithTimeout(tt.ctx, config.RequestTimeout)
defer cancel()
res, req := makeRequestResponse(ctx, "POST", "/api/shorts", tt.body)
server.ServeHTTP(res, req)
assert.Equal(t, tt.responseCode, res.Code)

View File

@ -600,6 +600,7 @@ func createNewID(ctx context.Context, db bun.IDB, model interface{}) (string, er
var newID string
maxIters := 10
for {
// Make sure we don't get stuck in an infinite loop
maxIters--