added more form functions

This commit is contained in:
2023-03-26 20:19:31 +02:00
parent 0af31bb0d9
commit 07bb5569a7
10 changed files with 200 additions and 91 deletions

View File

@@ -24,6 +24,8 @@ import (
"github.com/tlinden/cenophane/cfg" "github.com/tlinden/cenophane/cfg"
"github.com/tlinden/cenophane/common" "github.com/tlinden/cenophane/common"
"bytes"
"html/template"
"strings" "strings"
"time" "time"
) )
@@ -80,60 +82,8 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
return c.Status(fiber.StatusOK).JSON(res) return c.Status(fiber.StatusOK).JSON(res)
} }
/* // delete form
func FormFetch(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallExpire ...bool) error {
// deliver a file and delete it if expire is set to asap
// we ignore c.Params("file"), cause it may be malign. Also we've
// got it in the db anyway
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
if err != nil {
return fiber.NewError(403, "Invalid id provided!")
}
// retrieve the API Context name from the session
apicontext, err := GetApicontext(c)
if err != nil {
return JsonStatus(c, fiber.StatusInternalServerError,
"Unable to initialize session store from context: "+err.Error())
}
upload, err := db.Lookup(apicontext, id)
if err != nil {
// non existent db entry with that id, or other db error, see logs
return fiber.NewError(404, "No download with that id could be found!")
}
file := upload.File
filename := filepath.Join(cfg.StorageDir, id, file)
if _, err := os.Stat(filename); err != nil {
// db entry is there, but file isn't (anymore?)
go db.Delete(apicontext, id)
return fiber.NewError(404, "No download with that id could be found!")
}
// finally put the file to the client
err = c.Download(filename, file)
if len(shallExpire) > 0 {
if shallExpire[0] == true {
go func() {
// check if we need to delete the file now and do it in the background
if upload.Expire == "asap" {
cleanup(filepath.Join(cfg.StorageDir, id))
db.Delete(apicontext, id)
}
}()
}
}
return err
}
// delete file, id dir and db entry
func FormDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { func FormDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
id, err := common.Untaint(c.Params("id"), cfg.RegKey) id, err := common.Untaint(c.Params("id"), cfg.RegKey)
if err != nil { if err != nil {
return JsonStatus(c, fiber.StatusForbidden, return JsonStatus(c, fiber.StatusForbidden,
@@ -156,11 +106,9 @@ func FormDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
if err != nil { if err != nil {
// non existent db entry with that id, or other db error, see logs // non existent db entry with that id, or other db error, see logs
return JsonStatus(c, fiber.StatusForbidden, return JsonStatus(c, fiber.StatusForbidden,
"No upload with that id could be found!") "No form with that id could be found!")
} }
cleanup(filepath.Join(cfg.StorageDir, id))
return nil return nil
} }
@@ -187,20 +135,20 @@ func FormsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
} }
// get list // get list
uploads, err := db.FormsList(apicontext, filter) response, err := db.List(apicontext, filter, common.TypeForm)
if err != nil { if err != nil {
return JsonStatus(c, fiber.StatusForbidden, return JsonStatus(c, fiber.StatusForbidden,
"Unable to list uploads: "+err.Error()) "Unable to list forms: "+err.Error())
} }
// if we reached this point we can signal success // if we reached this point we can signal success
uploads.Success = true response.Success = true
uploads.Code = fiber.StatusOK response.Code = fiber.StatusOK
return c.Status(fiber.StatusOK).JSON(uploads) return c.Status(fiber.StatusOK).JSON(response)
} }
// returns just one upload obj + error code, no post processing by server // returns just one form obj + error code
func FormDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error { func FormDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
id, err := common.Untaint(c.Params("id"), cfg.RegKey) id, err := common.Untaint(c.Params("id"), cfg.RegKey)
if err != nil { if err != nil {
@@ -215,20 +163,55 @@ func FormDescribe(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
"Unable to initialize session store from context: "+err.Error()) "Unable to initialize session store from context: "+err.Error())
} }
uploads, err := db.Get(apicontext, id) response, err := db.Get(apicontext, id, common.TypeForm)
if err != nil { if err != nil {
return JsonStatus(c, fiber.StatusForbidden, return JsonStatus(c, fiber.StatusForbidden,
"No upload with that id could be found!") "No form with that id could be found!")
} }
for _, upload := range uploads.Entries { for _, form := range response.Forms {
upload.Url = strings.Join([]string{cfg.Url, "download", id, upload.File}, "/") form.Url = strings.Join([]string{cfg.Url, "form", id}, "/")
} }
// if we reached this point we can signal success // if we reached this point we can signal success
uploads.Success = true response.Success = true
uploads.Code = fiber.StatusOK response.Code = fiber.StatusOK
return c.Status(fiber.StatusOK).JSON(uploads) return c.Status(fiber.StatusOK).JSON(response)
} }
/*
Render the upload html form. Template given by --formpage, stored
as text in cfg.Formpage. It will be rendered using golang's
template engine, data to be filled in is the form matching the
given id.
*/ */
func FormPage(c *fiber.Ctx, cfg *cfg.Config, db *Db, shallexpire bool) error {
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
if err != nil {
return c.Status(fiber.StatusForbidden).SendString("Invalid id provided!")
}
apicontext, err := GetApicontext(c)
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Unable to initialize session store from context:" + err.Error())
}
response, err := db.Get(apicontext, id, common.TypeForm)
if err != nil || len(response.Forms) == 0 {
return c.Status(fiber.StatusForbidden).SendString("No form with that id could be found!")
}
t := template.New("form")
if t, err = t.Parse(cfg.Formpage); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Unable to load form template: " + err.Error())
}
var out bytes.Buffer
if err := t.Execute(&out, response.Forms[0]); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("Unable to render form template: " + err.Error())
}
c.Set("Content-type", "text/html; charset=utf-8")
return c.Status(fiber.StatusOK).SendString(out.String())
}

View File

@@ -86,23 +86,21 @@ func Runserver(conf *cfg.Config, args []string) error {
return FormCreate(c, conf, db) return FormCreate(c, conf, db)
}) })
/* // remove
// remove api.Delete("/forms/:id", auth, func(c *fiber.Ctx) error {
api.Delete("/forms/:id", auth, func(c *fiber.Ctx) error { err := FormDelete(c, conf, db)
err := FormDelete(c, conf, db) return SendResponse(c, "", err)
return SendResponse(c, "", err) })
})
// listing // listing
api.Get("/forms", auth, func(c *fiber.Ctx) error { api.Get("/forms", auth, func(c *fiber.Ctx) error {
return FormsList(c, conf, db) return FormsList(c, conf, db)
}) })
// info/describe // info/describe
api.Get("/forms/:id", auth, func(c *fiber.Ctx) error { api.Get("/forms/:id", auth, func(c *fiber.Ctx) error {
return FormDescribe(c, conf, db) return FormDescribe(c, conf, db)
}) })
*/
} }
// public routes // public routes
@@ -119,11 +117,10 @@ func Runserver(conf *cfg.Config, args []string) error {
return UploadFetch(c, conf, db, shallExpire) return UploadFetch(c, conf, db, shallExpire)
}) })
/* router.Get("/form/:id", func(c *fiber.Ctx) error {
router.Get("/form/:id", func(c *fiber.Ctx) error { return FormPage(c, conf, db, shallExpire)
return FormFetch(c, conf, db, shallExpire) })
})
*/
} }
// setup cleaner // setup cleaner

View File

@@ -34,6 +34,7 @@ type Apicontext struct {
// holds the whole configs, filled by commandline flags, env and config file // holds the whole configs, filled by commandline flags, env and config file
type Config struct { type Config struct {
// Flags+config file settings
ApiPrefix string `koanf:"apiprefix"` // path prefix ApiPrefix string `koanf:"apiprefix"` // path prefix
Debug bool `koanf:"debug"` Debug bool `koanf:"debug"`
Listen string `koanf:"listen"` // [host]:port Listen string `koanf:"listen"` // [host]:port
@@ -42,6 +43,7 @@ type Config struct {
DbFile string `koanf:"dbfile"` DbFile string `koanf:"dbfile"`
Super string `koanf:"super"` // the apicontext which has all permissions Super string `koanf:"super"` // the apicontext which has all permissions
Frontpage string `koanf:"frontpage"` // a html file Frontpage string `koanf:"frontpage"` // a html file
Formpage string `koanf:"formpage"` // a html file
// fiber settings, see: // fiber settings, see:
// https://docs.gofiber.io/api/fiber/#config // https://docs.gofiber.io/api/fiber/#config

View File

@@ -65,6 +65,7 @@ func Execute() error {
f.StringVarP(&conf.Super, "super", "", "", "The API Context which has permissions on all contexts") f.StringVarP(&conf.Super, "super", "", "", "The API Context which has permissions on all contexts")
f.StringVarP(&conf.Frontpage, "frontpage", "", "welcome to upload api, use /api enpoint!", f.StringVarP(&conf.Frontpage, "frontpage", "", "welcome to upload api, use /api enpoint!",
"Content or filename to be displayed on / in case someone visits") "Content or filename to be displayed on / in case someone visits")
f.StringVarP(&conf.Formpage, "formpage", "", "", "Content or filename to be displayed for forms (must be a go template)")
// server settings // server settings
f.BoolVarP(&conf.V4only, "ipv4", "4", false, "Only listen on ipv4") f.BoolVarP(&conf.V4only, "ipv4", "4", false, "Only listen on ipv4")
@@ -142,6 +143,20 @@ func Execute() error {
} }
} }
// Formpage?
if conf.Formpage != "" {
if _, err := os.Stat(conf.Formpage); err == nil {
// it's a filename, try to use it
content, err := ioutil.ReadFile(conf.Formpage)
if err != nil {
return errors.New("error loading config: " + err.Error())
}
// replace the filename
conf.Formpage = string(content)
}
}
switch { switch {
case ShowVersion: case ShowVersion:
fmt.Println(cfg.Getversion()) fmt.Println(cfg.Getversion())

View File

@@ -43,6 +43,9 @@ type Config struct {
// required to intercept requests using httpmock in tests // required to intercept requests using httpmock in tests
Mock bool Mock bool
// required for forms
Description string
} }
func Getversion() string { func Getversion() string {

72
upctl/cmd/formcommands.go Normal file
View File

@@ -0,0 +1,72 @@
/*
Copyright © 2023 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cmd
import (
//"errors"
"github.com/spf13/cobra"
"github.com/tlinden/cenophane/upctl/cfg"
"github.com/tlinden/cenophane/upctl/lib"
"os"
)
func FormCommand(conf *cfg.Config) *cobra.Command {
var formCmd = &cobra.Command{
Use: "form {create|delete|modify|list}",
Short: "Form commands",
Long: `Manage upload forms.`,
RunE: func(cmd *cobra.Command, args []string) error {
// errors at this stage do not cause the usage to be shown
//cmd.SilenceUsage = true
if len(args) == 0 {
cmd.Help()
os.Exit(0)
}
return nil
},
}
formCmd.Aliases = append(formCmd.Aliases, "frm")
formCmd.Aliases = append(formCmd.Aliases, "f")
formCmd.AddCommand(FormCreateCommand(conf))
return formCmd
}
func FormCreateCommand(conf *cfg.Config) *cobra.Command {
var formCreateCmd = &cobra.Command{
Use: "create [options]",
Short: "Create a new form",
Long: `Create a new form for consumers so they can upload something.`,
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.CreateForm(os.Stdout, conf)
},
}
// options
formCreateCmd.PersistentFlags().StringVarP(&conf.Expire, "expire", "e", "", "Expire setting: asap or duration (accepted shortcuts: dmh)")
formCreateCmd.PersistentFlags().StringVarP(&conf.Description, "description", "D", "", "Description of the form")
formCreateCmd.Aliases = append(formCreateCmd.Aliases, "add")
formCreateCmd.Aliases = append(formCreateCmd.Aliases, "+")
return formCreateCmd
}

View File

@@ -92,6 +92,7 @@ func Execute() {
rootCmd.AddCommand(DeleteCommand(&conf)) rootCmd.AddCommand(DeleteCommand(&conf))
rootCmd.AddCommand(DescribeCommand(&conf)) rootCmd.AddCommand(DescribeCommand(&conf))
rootCmd.AddCommand(DownloadCommand(&conf)) rootCmd.AddCommand(DownloadCommand(&conf))
rootCmd.AddCommand(FormCommand(&conf))
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {

View File

@@ -320,3 +320,29 @@ func Download(w io.Writer, c *cfg.Config, args []string) error {
return nil return nil
} }
/**** Forms stuff ****/
func CreateForm(w io.Writer, c *cfg.Config) error {
// setup url, req.Request, timeout handling etc
rq := Setup(c, "/forms")
// actual post w/ settings
resp, err := rq.R.
SetFormData(map[string]string{
"expire": c.Expire,
"description": c.Description,
}).
Post(rq.Url)
if err != nil {
return err
}
if err := HandleResponse(c, resp); err != nil {
return err
}
return RespondExtended(w, resp)
return nil
}

View File

@@ -35,7 +35,8 @@ func prepareExpire(expire string, start common.Timestamp) string {
case "asap": case "asap":
return "On first access" return "On first access"
default: default:
return time.Unix(start.Unix()+int64(common.Duration2int(expire)), 0).Format("2006-01-02 15:04:05") return time.Unix(start.Unix()+int64(common.Duration2int(expire)), 0).
Format("2006-01-02 15:04:05")
} }
return "" return ""
@@ -87,7 +88,16 @@ func WriteExtended(w io.Writer, response *common.Response) {
fmt.Fprintln(w) fmt.Fprintln(w)
} }
// FIXME: add response.Forms loop here for _, entry := range response.Forms {
expire := prepareExpire(entry.Expire, entry.Created)
fmt.Fprintf(w, format, "Id", entry.Id)
fmt.Fprintf(w, format, "Expire", expire)
fmt.Fprintf(w, format, "Context", entry.Context)
fmt.Fprintf(w, format, "Created", entry.Created)
fmt.Fprintf(w, format, "Description", entry.Description)
fmt.Fprintf(w, format, "Url", entry.Url)
fmt.Fprintln(w)
}
} }
// extract an common.Uploads{} struct from json response // extract an common.Uploads{} struct from json response