- added form list
- added sort by time
- fixed hanging channel in zip writer
- removed download url from form template
- added upload description
- added linter to Makefiles
- added missing test file
This commit is contained in:
2023-03-29 19:04:01 +02:00
parent c6f2b3f57c
commit 4aac69d425
16 changed files with 142 additions and 30 deletions

View File

@@ -27,9 +27,15 @@ COMMIT = $(shell git rev-parse --short=8 HEAD)
BUILD = $(shell date +%Y.%m.%d.%H%M%S) BUILD = $(shell date +%Y.%m.%d.%H%M%S)
VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version)) VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
HAVE_POD := $(shell pod2text -h 2>/dev/null) HAVE_POD := $(shell pod2text -h 2>/dev/null)
HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null)
DAEMON := ephemerupd DAEMON := ephemerupd
all: cmd/formtemplate.go buildlocal buildlocalctl all: cmd/formtemplate.go lint buildlocal buildlocalctl
lint:
ifdef HAVE_LINT
golangci-lint run
endif
buildlocalctl: buildlocalctl:
make -C upctl make -C upctl

View File

@@ -271,7 +271,7 @@ The `endpoint` is the **ephemerup** server running somewhere and the
- upctl: get rid of HandleResponse(), used only once anyway - upctl: get rid of HandleResponse(), used only once anyway
- add form so that public users can upload - add form so that public users can upload
- use Writer for output.go so we can unit test the stuff in there - use Writer for output.go so we can unit test the stuff in there
- add (default by time!) sorting to list outputs, and add sort flag
## BUGS ## BUGS

View File

@@ -180,6 +180,8 @@ func ZipDir(directory, zipfilename string) error {
if err != nil { if err != nil {
failure <- "Failed to zip directory: " + err.Error() failure <- "Failed to zip directory: " + err.Error()
} else {
close(failure)
} }
}() }()

View File

@@ -73,6 +73,15 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
entry.Notify = nt entry.Notify = nt
} }
if len(formdata.Description) != 0 {
des, err := common.Untaint(formdata.Description, cfg.RegText)
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
"Invalid description: "+err.Error())
}
entry.Description = des
}
// get url [and zip if there are multiple files] // get url [and zip if there are multiple files]
returnUrl := strings.Join([]string{cfg.Url, "form", id}, "/") returnUrl := strings.Join([]string{cfg.Url, "form", id}, "/")
entry.Url = returnUrl entry.Url = returnUrl

View File

@@ -72,6 +72,7 @@ type Config struct {
RegDuration *regexp.Regexp RegDuration *regexp.Regexp
RegKey *regexp.Regexp RegKey *regexp.Regexp
RegEmail *regexp.Regexp RegEmail *regexp.Regexp
RegText *regexp.Regexp
CleanInterval time.Duration CleanInterval time.Duration
DefaultExpire int DefaultExpire int
} }
@@ -118,8 +119,8 @@ func (c *Config) ApplyDefaults() {
c.RegNormalizedFilename = regexp.MustCompile(`[^\w\d\-_\.]`) c.RegNormalizedFilename = regexp.MustCompile(`[^\w\d\-_\.]`)
c.RegDuration = regexp.MustCompile(`[^dhms0-9]`) c.RegDuration = regexp.MustCompile(`[^dhms0-9]`)
c.RegKey = regexp.MustCompile(`[^a-zA-Z0-9\-]`) c.RegKey = regexp.MustCompile(`[^a-zA-Z0-9\-]`)
c.RegEmail = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) c.RegEmail = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9]`)
c.RegEmail = regexp.MustCompile(`[^a-z0-9._%+\-@0-9]`) c.RegText = regexp.MustCompile(`[^a-zA-Z0-9._%+\-@0-9 #/\.]`)
c.CleanInterval = 10 * time.Second c.CleanInterval = 10 * time.Second
c.DefaultExpire = 30 * 86400 // 1 month c.DefaultExpire = 30 * 86400 // 1 month

View File

@@ -30,6 +30,10 @@ const formtemplate = `
Use this form to upload one or more files. The creator of the form will automatically get notified. Use this form to upload one or more files. The creator of the form will automatically get notified.
</p> </p>
</div> </div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">Description</label>
<label class="col-sm-10 col-form-label">{{ .Description}} </label>
</div>
<div class="mb-3 row"> <div class="mb-3 row">
<label for="file" class="col-sm-2 col-form-label">Select</label> <label for="file" class="col-sm-2 col-form-label">Select</label>
<div class="col-sm-10"> <div class="col-sm-10">

View File

@@ -36,13 +36,14 @@ type Dbentry interface {
} }
type Upload struct { type Upload struct {
Id string `json:"id"` Id string `json:"id"`
Expire string `json:"expire"` Expire string `json:"expire"`
File string `json:"file"` // final filename (visible to the downloader) File string `json:"file"` // final filename (visible to the downloader)
Members []string `json:"members"` // contains multiple files, so File is an archive Members []string `json:"members"` // contains multiple files, so File is an archive
Created Timestamp `json:"uploaded"` Created Timestamp `json:"uploaded"`
Context string `json:"context"` Context string `json:"context"`
Url string `json:"url"` Description string `json:"description"`
Url string `json:"url"`
} }
// this one is also used for marshalling to the client // this one is also used for marshalling to the client

View File

@@ -27,6 +27,10 @@
Use this form to upload one or more files. The creator of the form will automatically get notified. Use this form to upload one or more files. The creator of the form will automatically get notified.
</p> </p>
</div> </div>
<div class="mb-3 row">
<label class="col-sm-2 col-form-label">Description</label>
<label class="col-sm-10 col-form-label">{{ .Description}} </label>
</div>
<div class="mb-3 row"> <div class="mb-3 row">
<label for="file" class="col-sm-2 col-form-label">Select</label> <label for="file" class="col-sm-2 col-form-label">Select</label>
<div class="col-sm-10"> <div class="col-sm-10">
@@ -71,8 +75,8 @@
$('.statusMsg').html(''); $('.statusMsg').html('');
if(response.success){ if(response.success){
$('#UploadForm')[0].reset(); $('#UploadForm')[0].reset();
$('.statusMsg').html('<p class="alert alert-success">Your upload is available at <code>' $('.statusMsg').html('<p class="alert alert-success">Your upload is available for download.<!-- '
+response.uploads[0].url+'</code> for download</p>'); +response.uploads[0].url+' -->');
$('#UploadForm').hide(); $('#UploadForm').hide();
}else{ }else{
$('.statusMsg').html('<p class="alert alert-danger">'+response.message+'</p>'); $('.statusMsg').html('<p class="alert alert-danger">'+response.message+'</p>');

View File

@@ -28,9 +28,14 @@ COMMIT = $(shell git rev-parse --short=8 HEAD)
BUILD = $(shell date +%Y.%m.%d.%H%M%S) BUILD = $(shell date +%Y.%m.%d.%H%M%S)
VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version)) VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
HAVE_POD := $(shell pod2text -h 2>/dev/null) HAVE_POD := $(shell pod2text -h 2>/dev/null)
HAVE_LINT:= $(shell golangci-lint -h 2>/dev/null)
all: buildlocal all: lint buildlocal
lint:
ifdef HAVE_LINT
golangci-lint run
endif
buildlocal: buildlocal:
go build -ldflags "-X 'github.com/tlinden/ephemerup/upctl/cfg.VERSION=$(VERSION)'" go build -ldflags "-X 'github.com/tlinden/ephemerup/upctl/cfg.VERSION=$(VERSION)'"

View File

@@ -19,6 +19,7 @@ package cmd
import ( import (
//"errors" //"errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tlinden/ephemerup/common"
"github.com/tlinden/ephemerup/upctl/cfg" "github.com/tlinden/ephemerup/upctl/cfg"
"github.com/tlinden/ephemerup/upctl/lib" "github.com/tlinden/ephemerup/upctl/lib"
"os" "os"
@@ -43,6 +44,7 @@ func FormCommand(conf *cfg.Config) *cobra.Command {
formCmd.Aliases = append(formCmd.Aliases, "f") formCmd.Aliases = append(formCmd.Aliases, "f")
formCmd.AddCommand(FormCreateCommand(conf)) formCmd.AddCommand(FormCreateCommand(conf))
formCmd.AddCommand(FormListCommand(conf))
return formCmd return formCmd
} }
@@ -70,3 +72,25 @@ func FormCreateCommand(conf *cfg.Config) *cobra.Command {
return formCreateCmd return formCreateCmd
} }
func FormListCommand(conf *cfg.Config) *cobra.Command {
var listCmd = &cobra.Command{
Use: "list [options]",
Short: "List formss",
Long: `List formss.`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true
return lib.List(os.Stdout, conf, nil, common.TypeForm)
},
}
// options
listCmd.PersistentFlags().StringVarP(&conf.Apicontext, "apicontext", "", "", "Filter by given API context")
listCmd.Aliases = append(listCmd.Aliases, "ls")
listCmd.Aliases = append(listCmd.Aliases, "l")
return listCmd
}

View File

@@ -19,6 +19,7 @@ package cmd
import ( import (
"errors" "errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tlinden/ephemerup/common"
"github.com/tlinden/ephemerup/upctl/cfg" "github.com/tlinden/ephemerup/upctl/cfg"
"github.com/tlinden/ephemerup/upctl/lib" "github.com/tlinden/ephemerup/upctl/lib"
"os" "os"
@@ -43,6 +44,7 @@ func UploadCommand(conf *cfg.Config) *cobra.Command {
// options // options
uploadCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)") uploadCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)")
uploadCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "", "Description of the form")
uploadCmd.Aliases = append(uploadCmd.Aliases, "up") uploadCmd.Aliases = append(uploadCmd.Aliases, "up")
uploadCmd.Aliases = append(uploadCmd.Aliases, "u") uploadCmd.Aliases = append(uploadCmd.Aliases, "u")
@@ -59,7 +61,7 @@ func ListCommand(conf *cfg.Config) *cobra.Command {
// errors at this stage do not cause the usage to be shown // errors at this stage do not cause the usage to be shown
cmd.SilenceUsage = true cmd.SilenceUsage = true
return lib.List(os.Stdout, conf, args) return lib.List(os.Stdout, conf, args, common.TypeUpload)
}, },
} }

View File

@@ -190,7 +190,8 @@ func UploadFiles(w io.Writer, c *cfg.Config, args []string) error {
// actual post w/ settings // actual post w/ settings
resp, err := rq.R. resp, err := rq.R.
SetFormData(map[string]string{ SetFormData(map[string]string{
"expire": c.Expire, "expire": c.Expire,
"description": c.Description,
}). }).
Post(rq.Url) Post(rq.Url)
@@ -205,8 +206,15 @@ func UploadFiles(w io.Writer, c *cfg.Config, args []string) error {
return RespondExtended(w, resp) return RespondExtended(w, resp)
} }
func List(w io.Writer, c *cfg.Config, args []string) error { func List(w io.Writer, c *cfg.Config, args []string, typ int) error {
rq := Setup(c, "/uploads") var rq *Request
switch typ {
case common.TypeUpload:
rq = Setup(c, "/uploads")
case common.TypeForm:
rq = Setup(c, "/forms")
}
params := &ListParams{Apicontext: c.Apicontext} params := &ListParams{Apicontext: c.Apicontext}
resp, err := rq.R. resp, err := rq.R.
@@ -221,7 +229,14 @@ func List(w io.Writer, c *cfg.Config, args []string) error {
return err return err
} }
return UploadsRespondTable(w, resp) switch typ {
case common.TypeUpload:
return UploadsRespondTable(w, resp)
case common.TypeForm:
return FormsRespondTable(w, resp)
}
return nil
} }
func Delete(w io.Writer, c *cfg.Config, args []string) error { func Delete(w io.Writer, c *cfg.Config, args []string) error {
@@ -332,10 +347,10 @@ func CreateForm(w io.Writer, c *cfg.Config) error {
// actual post w/ settings // actual post w/ settings
resp, err := rq.R. resp, err := rq.R.
SetFormData(map[string]string{ SetBody(&common.Form{
"expire": c.Expire, Expire: c.Expire,
"description": c.Description, Description: c.Description,
"notify": c.Notify, Notify: c.Notify,
}). }).
Post(rq.Url) Post(rq.Url)

View File

@@ -22,6 +22,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/jarcoal/httpmock" "github.com/jarcoal/httpmock"
"github.com/tlinden/ephemerup/common"
"github.com/tlinden/ephemerup/upctl/cfg" "github.com/tlinden/ephemerup/upctl/cfg"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -232,7 +233,7 @@ func TestList(t *testing.T) {
for _, unit := range tests { for _, unit := range tests {
var w bytes.Buffer var w bytes.Buffer
Intercept(unit) Intercept(unit)
Check(t, unit, &w, List(&w, conf, []string{})) Check(t, unit, &w, List(&w, conf, []string{}, common.TypeUpload))
} }
} }

View File

@@ -21,10 +21,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
//"github.com/alecthomas/repr"
"github.com/imroc/req/v3" "github.com/imroc/req/v3"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/tlinden/ephemerup/common" "github.com/tlinden/ephemerup/common"
"io" "io"
"sort"
"strings" "strings"
"time" "time"
) )
@@ -77,7 +79,8 @@ func WriteExtended(w io.Writer, response *common.Response) {
// we shall only have 1 element, however, if we ever support more, here we go // we shall only have 1 element, however, if we ever support more, here we go
for _, entry := range response.Uploads { for _, entry := range response.Uploads {
expire := prepareExpire(entry.Expire, entry.Created) expire := prepareExpire(entry.Expire, entry.Created)
fmt.Fprintf(w, format, "Id", entry.Id) fmt.Fprintf(w, format, "Upload-Id", entry.Id)
fmt.Fprintf(w, format, "Description", entry.Id)
fmt.Fprintf(w, format, "Expire", expire) fmt.Fprintf(w, format, "Expire", expire)
fmt.Fprintf(w, format, "Context", entry.Context) fmt.Fprintf(w, format, "Context", entry.Context)
fmt.Fprintf(w, format, "Created", entry.Created) fmt.Fprintf(w, format, "Created", entry.Created)
@@ -88,18 +91,18 @@ func WriteExtended(w io.Writer, response *common.Response) {
for _, entry := range response.Forms { for _, entry := range response.Forms {
expire := prepareExpire(entry.Expire, entry.Created) expire := prepareExpire(entry.Expire, entry.Created)
fmt.Fprintf(w, format, "Id", entry.Id) fmt.Fprintf(w, format, "Form-Id", entry.Id)
fmt.Fprintf(w, format, "Description", entry.Description)
fmt.Fprintf(w, format, "Expire", expire) fmt.Fprintf(w, format, "Expire", expire)
fmt.Fprintf(w, format, "Context", entry.Context) fmt.Fprintf(w, format, "Context", entry.Context)
fmt.Fprintf(w, format, "Created", entry.Created) fmt.Fprintf(w, format, "Created", entry.Created)
fmt.Fprintf(w, format, "Description", entry.Description)
fmt.Fprintf(w, format, "Notify", entry.Notify) fmt.Fprintf(w, format, "Notify", entry.Notify)
fmt.Fprintf(w, format, "Url", entry.Url) fmt.Fprintf(w, format, "Url", entry.Url)
fmt.Fprintln(w) fmt.Fprintln(w)
} }
} }
// extract an common.Uploads{} struct from json response // extract an common.Response{} struct from json response
func GetResponse(resp *req.Response) (*common.Response, error) { func GetResponse(resp *req.Response) (*common.Response, error) {
response := common.Response{} response := common.Response{}
@@ -125,15 +128,49 @@ func UploadsRespondTable(w io.Writer, resp *req.Response) error {
fmt.Fprintln(w, response.Message) fmt.Fprintln(w, response.Message)
} }
sort.SliceStable(response.Uploads, func(i, j int) bool {
return response.Uploads[i].Created.Time.Unix() < response.Uploads[j].Created.Time.Unix()
})
// tablewriter // tablewriter
data := [][]string{} data := [][]string{}
for _, entry := range response.Uploads { for _, entry := range response.Uploads {
data = append(data, []string{ data = append(data, []string{
entry.Id, entry.Expire, entry.Context, entry.Created.Format("2006-01-02 15:04:05"), entry.Id, entry.Description, entry.Expire, entry.Context,
entry.Created.Format("2006-01-02 15:04:05"), entry.File,
}) })
} }
WriteTable(w, []string{"ID", "EXPIRE", "CONTEXT", "CREATED"}, data) WriteTable(w, []string{"UPLOAD-ID", "DESCRIPTION", "EXPIRE", "CONTEXT", "CREATED", "FILE"}, data)
return nil
}
// turn the Forms{} struct into a table and print it
func FormsRespondTable(w io.Writer, resp *req.Response) error {
response, err := GetResponse(resp)
if err != nil {
return err
}
if response.Message != "" {
fmt.Fprintln(w, response.Message)
}
sort.SliceStable(response.Forms, func(i, j int) bool {
return response.Forms[i].Created.Time.Unix() < response.Forms[j].Created.Time.Unix()
})
// tablewriter
data := [][]string{}
for _, entry := range response.Forms {
data = append(data, []string{
entry.Id, entry.Description, entry.Expire, entry.Context,
entry.Created.Format("2006-01-02 15:04:05"), entry.Notify,
})
}
WriteTable(w, []string{"FORM-ID", "DESCRIPTION", "EXPIRE", "CONTEXT", "CREATED", "NOTIFY"}, data)
return nil return nil
} }

View File

View File

@@ -1,2 +1,3 @@
endpoint = "http://localhost:8080/v1" endpoint = "http://localhost:8080/v1"
apikey = "970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a" apikey = "970b391f22f515d96b3e9b86a2c62c627968828e47b356994d2e583188b4190a"
notify = "root@localhost"