Browse Source

up/down and dir now work with url param q

  I was using `echo.Context.Param(string) string` but found it wasn't
parsing the url params at all, instead `echo.Context.QueryParam(string)
string` is being used, which works.

 /up will now write/rewrite a json file.
 /down will now read and send a json file.
 /dir will now read a directory and list files and directories in
a recursive manner.

 / remains unchanged.
Apollo 1 year ago
parent
commit
12f2d84e19
5 changed files with 347 additions and 1 deletions
  1. 3 1
      README.md
  2. 19 0
      go.mod
  3. 42 0
      go.sum
  4. 239 0
      handles.go
  5. 44 0
      main.go

+ 3 - 1
README.md

@@ -1,3 +1,5 @@
 # JGoServer
 
-A JSON Golang server, built with flexibility for any Kaboom.js game
+A JSON Golang server, built with flexibility for any Kaboom.js game.
+
+This is a POC for Web-BBS, a BBS software for the Web/Internet of things.

+ 19 - 0
go.mod

@@ -0,0 +1,19 @@
+module git.red-green.com/david/jgoserver
+
+go 1.20
+
+require (
+	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
+	github.com/labstack/echo/v4 v4.10.2 // indirect
+	github.com/labstack/gommon v0.4.0 // indirect
+	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-isatty v0.0.17 // indirect
+	github.com/nyudlts/bytemath v0.0.0-20220916185312-3642ac9d63b8 // indirect
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/valyala/fasttemplate v1.2.2 // indirect
+	golang.org/x/crypto v0.6.0 // indirect
+	golang.org/x/net v0.7.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/time v0.3.0 // indirect
+)

+ 42 - 0
go.sum

@@ -0,0 +1,42 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
+github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
+github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
+github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
+github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
+github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/nyudlts/bytemath v0.0.0-20220916185312-3642ac9d63b8 h1:1K5cHwIaFC6RDcgDJI2iKFZ6woxb9FaXKhY8FfkasJU=
+github.com/nyudlts/bytemath v0.0.0-20220916185312-3642ac9d63b8/go.mod h1:NS8NN4L8KbjQdRvexw/gExZRkfqrT8oGCkVnPo/Q7mk=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
+github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 239 - 0
handles.go

@@ -0,0 +1,239 @@
+package main
+
+import (
+	"encoding/json"
+	"errors"
+	"io"
+	"net/http"
+	"os"
+	"path"
+	"strings"
+
+	"github.com/labstack/echo/v4"
+	"github.com/nyudlts/bytemath"
+)
+
+// GET /
+func MainHandle(c echo.Context) error {
+	m := map[string]any{
+		"cod":     200,
+		"id":      "JGoServer",
+		"version": "1.0-dev",
+	}
+	ents, err := os.ReadDir("storage")
+	if errors.Is(err, os.ErrNotExist) {
+		err2 := os.Mkdir("storage", 0775)
+		if err2 != nil {
+			m["cod"] = 500
+			m["dirs"] = nil // This is what we were trying to populate
+			m["err"] = err2.Error()
+			m["cause"] = err.Error()
+			return c.JSON(m["cod"].(int), m)
+		}
+	} else if err != nil {
+		m["cod"] = 500
+		m["dirs"] = nil // This is what we were trying to populate
+		m["err"] = err.Error()
+		return c.JSON(m["cod"].(int), m)
+	}
+	dirs := []string{}
+	for _, e := range ents {
+		if e.IsDir() {
+			dirs = append(dirs, e.Name())
+		}
+	}
+	m["dirs"] = dirs
+	return c.JSON(m["cod"].(int), m)
+}
+
+// Sanitizes directory name so it's a little safe
+func CleanDirName(raw string) string {
+	return strings.ToLower(strings.Trim(raw, " .\r\n\t"))
+}
+
+// GET /dir?q=<dir>
+func GetDirHandle(c echo.Context) error {
+	m := map[string]any{
+		"cod": 200,
+	}
+	target := CleanDirName(c.QueryParam("q")) // Directory to list
+	if len(target) == 0 || target == "" {
+		// Nothing todo?
+		m["cod"] = http.StatusBadRequest
+		m["q"] = c.QueryParam("q")
+		return c.JSON(m["cod"].(int), m)
+	}
+	// Verify the directory at least exists
+	_, err := os.Stat(path.Join("storage", target))
+	if errors.Is(err, os.ErrNotExist) {
+		err2 := os.MkdirAll(path.Join("storage", target), 0775)
+		if err2 != nil {
+			m["cod"] = 500
+			m["files"] = nil
+			m["dirs"] = nil
+			m["err"] = err2.Error()
+			m["cause"] = err.Error()
+			return c.JSON(m["cod"].(int), m)
+		}
+	} else if err != nil {
+		m["cod"] = 500
+		m["files"] = nil
+		m["dirs"] = nil
+		m["err"] = err.Error()
+		return c.JSON(m["cod"].(int), m)
+	}
+	// Scan the dirs looking for files (won't include ones that start with .)
+	var scanDir func(string) ([]string, []string, error)
+	scanDir = func(dir string) ([]string, []string, error) {
+		ents, err := os.ReadDir(dir)
+		if err != nil {
+			return nil, nil, err
+		}
+		fs := []string{}
+		ds := []string{}
+		for _, e := range ents {
+			if strings.HasPrefix(e.Name(), ".") { // Keep hidden files/dirs hidden
+				continue
+			}
+			if e.IsDir() {
+				ds = append(ds, path.Join(dir, e.Name()))
+				f, d, err := scanDir(path.Join(dir, e.Name()))
+				if err != nil {
+					return nil, nil, err
+				}
+				fs = append(fs, f...)
+				ds = append(ds, d...)
+			} else {
+				fs = append(fs, path.Join(dir, e.Name()))
+			}
+		}
+		return fs, ds, nil
+	}
+	files, dirs, err2 := scanDir(path.Join("storage", target))
+	if err2 != nil {
+		m["cod"] = 500
+		m["files"] = nil
+		m["dirs"] = nil
+		m["err"] = err.Error()
+		return c.JSON(m["cod"].(int), m)
+	}
+	m["files"] = files
+	m["dirs"] = dirs
+	return c.JSON(m["cod"].(int), m)
+}
+
+// POST/PUT /up?q=<filepath>
+func UploadJData(c echo.Context) error {
+	// e.g. test/dir1/pkt001.json
+	targetDir := CleanDirName(path.Dir(c.QueryParam("q"))) // e.g. test/dir1
+	targetName := path.Base(c.QueryParam("q"))             // e.g. pkt001.json
+	_, err := os.Stat(path.Join("storage", targetDir))
+	if errors.Is(err, os.ErrNotExist) {
+		err2 := os.MkdirAll(path.Join("storage", targetDir), 0775)
+		if err2 != nil {
+			return c.JSON(500, map[string]any{
+				"cod":   500,
+				"err":   err2.Error(),
+				"cause": err.Error(),
+			})
+		}
+	} else if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod": 500,
+			"err": err.Error(),
+		})
+	}
+	fh, err := os.Create(path.Join("storage", targetDir, targetName))
+	if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod": 500,
+			"err": err.Error(),
+		})
+	}
+	body := c.Request().Body
+	defer body.Close()
+	written, err := io.Copy(fh, body)
+	fh.Close()
+	if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod": 500,
+			"err": err.Error(),
+		})
+	}
+	// Check that it is in fact really JSON
+	data, err := os.ReadFile(path.Join("storage", targetDir, targetName))
+	if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod": 500,
+			"err": err.Error(),
+		})
+	}
+	var test any
+	err = json.Unmarshal(data, &test)
+	if err != nil {
+		// Not json!
+		err2 := os.Remove(path.Join("storage", targetDir, targetName))
+		if err2 != nil {
+			return c.JSON(500, map[string]any{
+				"cod":   500,
+				"err":   err2.Error(),
+				"cause": err.Error(),
+			})
+		}
+		return c.JSON(500, map[string]any{
+			"cod": 500,
+			"err": err.Error(),
+		})
+	}
+	// Ok we're good
+	return c.JSON(200, map[string]any{
+		"cod":  200,
+		"size": bytemath.ConvertBytesToHumanReadable(written),
+	})
+}
+
+// GET /down?q=<filepath>
+func DownloadJData(c echo.Context) error {
+	// e.g. test/dir1/pkt001.json
+	targetDir := CleanDirName(path.Dir(c.QueryParam("q"))) // e.g. test/dir1
+	targetName := path.Base(c.QueryParam("q"))             // e.g. pkt001.json
+	_, err := os.ReadDir(path.Join("storage", targetDir))
+	if errors.Is(err, os.ErrNotExist) {
+		err2 := os.MkdirAll(path.Join("storage", targetDir), 0775)
+		if err2 != nil {
+			return c.JSON(500, map[string]any{
+				"cod":   500,
+				"err":   err2.Error(),
+				"cause": err.Error(),
+			})
+		}
+	} else if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod": 500,
+			"err": err.Error(),
+		})
+	}
+	// File contents must be JSON, we live in JSON so it must be JSON data
+	fh, err := os.ReadFile(path.Join("storage", targetDir, targetName))
+	if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod":  500,
+			"err":  "ReadFile: " + err.Error(),
+			"data": nil,
+		})
+	}
+	var data any
+	err = json.Unmarshal(fh, &data)
+	if err != nil {
+		return c.JSON(500, map[string]any{
+			"cod":  500,
+			"err":  "json.Unmarshal: " + err.Error(),
+			"data": nil,
+		})
+	}
+	return c.JSON(200, map[string]any{
+		"cod":  200,
+		"data": data,
+		"size": bytemath.ConvertBytesToHumanReadable(int64(len(fh))),
+	})
+}

+ 44 - 0
main.go

@@ -0,0 +1,44 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"os"
+
+	"github.com/labstack/echo/v4"
+	"github.com/labstack/echo/v4/middleware"
+)
+
+func main() {
+	_, err := os.Stat("storage")
+	if errors.Is(err, os.ErrNotExist) {
+		err2 := os.Mkdir("storage", 0775)
+		if err2 != nil {
+			log.Panic(map[string]error{
+				"err":   err2,
+				"cause": err,
+			})
+		}
+	}
+
+	E := echo.New()
+	E.Use(middleware.CORS())
+
+	E.GET("/", MainHandle)        // /                   (Mostly version info, lists valid dirs)
+	E.GET("/dir", GetDirHandle)   // /dir?q=<dir>        (lists dirs and files in absolute path)
+	E.POST("/up", UploadJData)    // /up?q=<filepath>    (Request.Body is saved into file)
+	E.PUT("/up", UploadJData)     // /up?q=<filepath>    (Request.Body is saved into file)
+	E.GET("/down", DownloadJData) // /down?q=<filepath>  (Obtains the json data of the file)
+
+	// Just prints out the paths
+	r := E.Routes()
+	for _, ro := range r {
+		fmt.Printf("%-5s http://127.0.0.1:9000%s\n", ro.Method, ro.Path)
+	}
+
+	err = E.Start("0.0.0.0:9000")
+	if err != nil {
+		log.Panic(err)
+	}
+}