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/common"
"bytes"
"html/template"
"strings"
"time"
)
@@ -80,60 +82,8 @@ func FormCreate(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
return c.Status(fiber.StatusOK).JSON(res)
}
/*
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
// delete form
func FormDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
if err != nil {
return JsonStatus(c, fiber.StatusForbidden,
@@ -156,11 +106,9 @@ func FormDelete(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
if err != nil {
// non existent db entry with that id, or other db error, see logs
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
}
@@ -187,20 +135,20 @@ func FormsList(c *fiber.Ctx, cfg *cfg.Config, db *Db) error {
}
// get list
uploads, err := db.FormsList(apicontext, filter)
response, err := db.List(apicontext, filter, common.TypeForm)
if err != nil {
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
uploads.Success = true
uploads.Code = fiber.StatusOK
response.Success = true
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 {
id, err := common.Untaint(c.Params("id"), cfg.RegKey)
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())
}
uploads, err := db.Get(apicontext, id)
response, err := db.Get(apicontext, id, common.TypeForm)
if err != nil {
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 {
upload.Url = strings.Join([]string{cfg.Url, "download", id, upload.File}, "/")
for _, form := range response.Forms {
form.Url = strings.Join([]string{cfg.Url, "form", id}, "/")
}
// if we reached this point we can signal success
uploads.Success = true
uploads.Code = fiber.StatusOK
response.Success = true
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)
})
/*
// remove
api.Delete("/forms/:id", auth, func(c *fiber.Ctx) error {
err := FormDelete(c, conf, db)
return SendResponse(c, "", err)
})
// remove
api.Delete("/forms/:id", auth, func(c *fiber.Ctx) error {
err := FormDelete(c, conf, db)
return SendResponse(c, "", err)
})
// listing
api.Get("/forms", auth, func(c *fiber.Ctx) error {
return FormsList(c, conf, db)
})
// listing
api.Get("/forms", auth, func(c *fiber.Ctx) error {
return FormsList(c, conf, db)
})
// info/describe
api.Get("/forms/:id", auth, func(c *fiber.Ctx) error {
return FormDescribe(c, conf, db)
})
*/
// info/describe
api.Get("/forms/:id", auth, func(c *fiber.Ctx) error {
return FormDescribe(c, conf, db)
})
}
// public routes
@@ -119,11 +117,10 @@ func Runserver(conf *cfg.Config, args []string) error {
return UploadFetch(c, conf, db, shallExpire)
})
/*
router.Get("/form/:id", func(c *fiber.Ctx) error {
return FormFetch(c, conf, db, shallExpire)
})
*/
router.Get("/form/:id", func(c *fiber.Ctx) error {
return FormPage(c, conf, db, shallExpire)
})
}
// setup cleaner

View File

@@ -34,6 +34,7 @@ type Apicontext struct {
// holds the whole configs, filled by commandline flags, env and config file
type Config struct {
// Flags+config file settings
ApiPrefix string `koanf:"apiprefix"` // path prefix
Debug bool `koanf:"debug"`
Listen string `koanf:"listen"` // [host]:port
@@ -42,6 +43,7 @@ type Config struct {
DbFile string `koanf:"dbfile"`
Super string `koanf:"super"` // the apicontext which has all permissions
Frontpage string `koanf:"frontpage"` // a html file
Formpage string `koanf:"formpage"` // a html file
// fiber settings, see:
// 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.Frontpage, "frontpage", "", "welcome to upload api, use /api enpoint!",
"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
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 {
case ShowVersion:
fmt.Println(cfg.Getversion())

View File

@@ -43,6 +43,9 @@ type Config struct {
// required to intercept requests using httpmock in tests
Mock bool
// required for forms
Description 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(DescribeCommand(&conf))
rootCmd.AddCommand(DownloadCommand(&conf))
rootCmd.AddCommand(FormCommand(&conf))
err := rootCmd.Execute()
if err != nil {

View File

@@ -320,3 +320,29 @@ func Download(w io.Writer, c *cfg.Config, args []string) error {
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":
return "On first access"
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 ""
@@ -87,7 +88,16 @@ func WriteExtended(w io.Writer, response *common.Response) {
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