Explorar o código

various fixes

Aneurin Barker Snook hai 11 meses
pai
achega
a6d06a5b26
Modificáronse 9 ficheiros con 148 adicións e 15 borrados
  1. 3 0
      README.md
  2. 4 2
      go.mod
  3. 2 3
      go.sum
  4. 62 8
      internal/api/api.go
  5. 1 0
      internal/cli/cli.go
  6. 60 0
      internal/cli/list.go
  7. 8 2
      internal/cli/start.go
  8. 6 0
      internal/types/redirect.go
  9. 2 0
      main.go

+ 3 - 0
README.md

@@ -39,6 +39,9 @@ You can use the following commands to manage your redirects:
 - `shorty add <path> <destination>` adds a redirect
 - `shorty rm <path>` removes a redirect
 - `shorty get <path>` gets the destination of a redirect
+- `shorty list <path>` lists all redirects
+
+> :warning: You can add, remove, or get any path _other than_ `/*` (the default "all-path"). This is a special case used for listing redirects and requires authentication if `--token|-t|SHORTY_TOKEN` is set.
 
 These commands connect to the running Shorty instance, so you need to make sure you have started it first. Make sure to set `--token|-t|SHORTY_TOKEN` as well if you have enabled token authentication.
 

+ 4 - 2
go.mod

@@ -4,7 +4,7 @@ go 1.21
 
 require (
 	github.com/alecthomas/kong v0.9.0
-	github.com/annybs/ezdb v0.0.0-20240621195216-a3d8644ce481
+	github.com/annybs/ezdb v0.0.0-20240709225824-fa988cf3e792
 	github.com/annybs/go/rest v0.0.0-20240622161209-822dcc4b52be
 	github.com/annybs/go/validate v0.0.0-20240621205444-13a966214726
 	github.com/rs/zerolog v1.33.0
@@ -13,7 +13,7 @@ require (
 
 require (
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
-	github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
+	github.com/golang/snappy v0.0.4 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
@@ -36,3 +36,5 @@ require (
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
+
+replace github.com/annybs/ezdb => /home/aneurin/Documents/github.com/annybs/ezdb

+ 2 - 3
go.sum

@@ -44,8 +44,6 @@ github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA
 github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/annybs/ezdb v0.0.0-20240621195216-a3d8644ce481 h1:cjRW2GKH/q9EfTt2zzYXNPbBfnGgWYjYKW6mCt3qxtM=
-github.com/annybs/ezdb v0.0.0-20240621195216-a3d8644ce481/go.mod h1:eWik4kAFQYV94eUfDYnDHYV5bz4+cmqmuHfjBs6qM3s=
 github.com/annybs/go/rest v0.0.0-20240622161209-822dcc4b52be h1:UnMe8ubB0C+LBi8EWl7JUFLj+KiyWkL3VGG6K2PS8WI=
 github.com/annybs/go/rest v0.0.0-20240622161209-822dcc4b52be/go.mod h1:Rlyy9gpI2n0HM/NBFSMBQ5f8hdH36pSCIpvCNWndMMU=
 github.com/annybs/go/validate v0.0.0-20240621205444-13a966214726 h1:sbBrFZbXtyFP9TNTTmbOCTu7ygCObj3aO6BZI/ZK3rY=
@@ -103,8 +101,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/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=

+ 62 - 8
internal/api/api.go

@@ -1,20 +1,23 @@
 package api
 
 import (
+	"encoding/json"
 	"io"
 	"net/http"
 
 	"github.com/annybs/ezdb"
 	"github.com/annybs/go/rest"
 	"github.com/annybs/go/validate"
+	"github.com/annybs/shorty/internal/types"
 	"github.com/rs/zerolog"
 )
 
 type API struct {
-	DB  ezdb.Collection[[]byte]
+	DB  ezdb.Collection[*types.Redirect]
 	Log zerolog.Logger
 
-	Token string
+	AllPath string
+	Token   string
 }
 
 func (api *API) Delete(w http.ResponseWriter, req *http.Request) {
@@ -24,6 +27,11 @@ func (api *API) Delete(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
+	if req.URL.Path == api.AllPath {
+		w.WriteHeader(http.StatusBadRequest)
+		return
+	}
+
 	path := req.URL.Path
 
 	if exist, _ := api.DB.Has(path); !exist {
@@ -40,18 +48,54 @@ func (api *API) Delete(w http.ResponseWriter, req *http.Request) {
 }
 
 func (api *API) Get(w http.ResponseWriter, req *http.Request) {
+	if req.URL.Path == api.AllPath {
+		api.GetAll(w, req)
+		return
+	}
+
 	path := req.URL.Path
 
-	dest, _ := api.DB.Get(path)
-	if len(dest) > 0 {
-		w.Header().Set("Location", string(dest))
-		w.WriteHeader(http.StatusPermanentRedirect)
-	} else {
+	redirect, err := api.DB.Get(path)
+	if err != nil {
 		w.WriteHeader(http.StatusNotFound)
+		w.Write([]byte(err.Error()))
+		return
 	}
+
+	if len(redirect.Destination) == 0 || redirect.StatusCode == 0 {
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write([]byte("invalid redirect"))
+	}
+
+	w.Header().Set("Location", string(redirect.Destination))
+	w.WriteHeader(redirect.StatusCode)
 	w.Write([]byte{})
 }
 
+func (api *API) GetAll(w http.ResponseWriter, req *http.Request) {
+	if !rest.IsAuthenticated(req, api.Token) {
+		w.WriteHeader(http.StatusUnauthorized)
+		w.Write([]byte{})
+		return
+	}
+
+	all, err := api.DB.Iter().GetAll()
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write([]byte(err.Error()))
+		return
+	}
+
+	data, err := json.Marshal(all)
+	if err != nil {
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write([]byte(err.Error()))
+		return
+	}
+
+	w.Write(data)
+}
+
 func (api *API) Put(w http.ResponseWriter, req *http.Request) {
 	if !rest.IsAuthenticated(req, api.Token) {
 		w.WriteHeader(http.StatusUnauthorized)
@@ -59,6 +103,11 @@ func (api *API) Put(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
+	if req.URL.Path == api.AllPath {
+		w.WriteHeader(http.StatusBadRequest)
+		return
+	}
+
 	path := req.URL.Path
 
 	dest, err := io.ReadAll(req.Body)
@@ -73,7 +122,12 @@ func (api *API) Put(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	if err := api.DB.Put(path, dest); err != nil {
+	redirect := &types.Redirect{
+		Destination: string(dest),
+		StatusCode:  http.StatusPermanentRedirect,
+	}
+
+	if err := api.DB.Put(path, redirect); err != nil {
 		w.WriteHeader(http.StatusInternalServerError)
 		w.Write([]byte(err.Error()))
 	} else {

+ 1 - 0
internal/cli/cli.go

@@ -13,6 +13,7 @@ type Context struct {
 type CLI struct {
 	Add   AddCmd   `cmd:"" help:"Add a redirect"`
 	Get   GetCmd   `cmd:"" help:"Get a redirect"`
+	List  ListCmd  `cmd:"" help:"List all redirects"`
 	Rm    RmCmd    `cmd:"" help:"Delete a redirect"`
 	Start StartCmd `cmd:"" help:"Start Shorty"`
 }

+ 60 - 0
internal/cli/list.go

@@ -0,0 +1,60 @@
+package cli
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+
+	"github.com/annybs/shorty/internal/types"
+)
+
+type ListCmd struct {
+	AllPath string `help:"All-path (SHORTY_ALL_PATH)" default:"${all_path}"`
+	Token   string `short:"t" help:"Bearer token (SHORTY_TOKEN)" default:"${token}"`
+	URL     string `short:"u" help:"Shorty URL (SHORTY_URL)" default:"${url}"`
+}
+
+func (c *ListCmd) Run(ctx *Context) error {
+	url := strings.Join([]string{c.URL, c.AllPath}, "")
+	req, err := http.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		return err
+	}
+	if c.Token != "" {
+		req.Header.Add("authorization", fmt.Sprintf("bearer %s", c.Token))
+	}
+
+	res, err := http.DefaultClient.Do(req)
+	if err != nil {
+		return err
+	}
+	if res.StatusCode != http.StatusOK {
+		body, err := io.ReadAll(res.Body)
+		if err != nil {
+			return err
+		}
+		if len(body) > 0 {
+			return errors.New(string(body))
+		}
+		return errors.New(res.Status)
+	}
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+
+	data := map[string]*types.Redirect{}
+	if err := json.Unmarshal(body, &data); err != nil {
+		return err
+	}
+
+	for from, redirect := range data {
+		fmt.Printf("%s %d %s\n", from, redirect.StatusCode, redirect.Destination)
+	}
+
+	return nil
+}

+ 8 - 2
internal/cli/start.go

@@ -6,9 +6,11 @@ import (
 
 	"github.com/annybs/ezdb"
 	"github.com/annybs/shorty/internal/api"
+	"github.com/annybs/shorty/internal/types"
 )
 
 type StartCmd struct {
+	AllPath      string `help:"All-path (SHORTY_ALL_PATH)" default:"${all_path}"`
 	DatabasePath string `short:"d" help:"Database path (SHORTY_DATABASE_PATH)" default:"${database_path}"`
 	Host         string `short:"h" help:"HTTP bind host (SHORTY_HOST)" default:"${host}"`
 	Port         string `short:"p" help:"HTTP port (SHORTY_PORT)" default:"${port}"`
@@ -18,8 +20,11 @@ type StartCmd struct {
 func (c *StartCmd) Run(ctx *Context) error {
 	errc := make(chan error)
 
+	marshaler := ezdb.JSON[*types.Redirect](func() *types.Redirect {
+		return &types.Redirect{}
+	})
 	db := ezdb.Memory(
-		ezdb.LevelDB(c.DatabasePath, ezdb.Bytes(), nil),
+		ezdb.LevelDB(c.DatabasePath, marshaler, nil),
 	)
 	if err := db.Open(); err != nil {
 		return err
@@ -29,7 +34,8 @@ func (c *StartCmd) Run(ctx *Context) error {
 		DB:  db,
 		Log: ctx.Log,
 
-		Token: c.Token,
+		AllPath: c.AllPath,
+		Token:   c.Token,
 	}
 
 	addr := net.JoinHostPort(c.Host, c.Port)

+ 6 - 0
internal/types/redirect.go

@@ -0,0 +1,6 @@
+package types
+
+type Redirect struct {
+	Destination string `json:"destination"`
+	StatusCode  int    `json:"statusCode"`
+}

+ 2 - 0
main.go

@@ -11,6 +11,7 @@ func main() {
 	// Initialise config from environment
 	config := viper.New()
 	config.SetEnvPrefix("shorty")
+	config.SetDefault("all_path", "/*")
 	config.SetDefault("database_path", ".shorty")
 	config.SetDefault("port", "3000")
 	config.SetDefault("url", "http://localhost:3000")
@@ -21,6 +22,7 @@ func main() {
 
 	// Initialise and run CLI
 	vars := kong.Vars{
+		"all_path":      config.GetString("all_path"),
 		"database_path": config.GetString("database_path"),
 		"host":          config.GetString("host"),
 		"port":          config.GetString("port"),