From 0af53e44d65c0fa03b17c3a5ba89df77526ed68e Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Fri, 20 Dec 2024 18:59:59 +0100 Subject: [PATCH] started with serve command, yet incomplete --- app/db.go | 1 + cfg/config.go | 1 + cmd/maincommands.go | 21 ++++++++ cmd/root.go | 1 + go.mod | 14 +++++- go.sum | 37 ++++++++++++++ rest/handlers.go | 118 ++++++++++++++++++++++++++++++++++++++++++++ rest/serve.go | 113 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 rest/handlers.go create mode 100644 rest/serve.go diff --git a/app/db.go b/app/db.go index 94ab9e3..a405272 100644 --- a/app/db.go +++ b/app/db.go @@ -254,6 +254,7 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) { } func (db *DB) Del(attr *DbAttr) error { + // FIXME: check if it exists prior to just call bucket.Delete()? if err := db.Open(); err != nil { return err } diff --git a/cfg/config.go b/cfg/config.go index 339bf02..5f0e2ca 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -15,4 +15,5 @@ type Config struct { DB *app.DB File string Tags []string + Listen string } diff --git a/cmd/maincommands.go b/cmd/maincommands.go index 7ff95c4..8c4639a 100644 --- a/cmd/maincommands.go +++ b/cmd/maincommands.go @@ -13,6 +13,7 @@ import ( "github.com/tlinden/anydb/app" "github.com/tlinden/anydb/cfg" "github.com/tlinden/anydb/output" + "github.com/tlinden/anydb/rest" ) func Set(conf *cfg.Config) *cobra.Command { @@ -311,3 +312,23 @@ func Man(conf *cfg.Config) *cobra.Command { return cmd } + +func Serve(conf *cfg.Config) *cobra.Command { + var cmd = &cobra.Command{ + Use: "serve [-l host:port]", + Short: "run REST API listener", + Long: `run REST API listener`, + RunE: func(cmd *cobra.Command, args []string) error { + // errors at this stage do not cause the usage to be shown + cmd.SilenceUsage = true + + rest.Runserver(conf, nil) + + return nil + }, + } + + cmd.PersistentFlags().StringVarP(&conf.Listen, "listen", "l", "localhost:8787", "host:port") + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index 1aacf0c..39a0188 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -83,6 +83,7 @@ func Execute() { rootCmd.AddCommand(Del(&conf)) rootCmd.AddCommand(Export(&conf)) rootCmd.AddCommand(Import(&conf)) + rootCmd.AddCommand(Serve(&conf)) rootCmd.AddCommand(Man(&conf)) err = rootCmd.Execute() diff --git a/go.mod b/go.mod index b29e5ac..2b07973 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,25 @@ go 1.22.1 require ( github.com/alecthomas/repr v0.4.0 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gofiber/fiber/v2 v2.52.5 // indirect + github.com/gofiber/fiber/v3 v3.0.0-beta.3 // indirect + github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect go.etcd.io/bbolt v1.3.11 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index c0a3043..f3c00a8 100644 --- a/go.sum +++ b/go.sum @@ -2,25 +2,52 @@ github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= 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/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac= github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v3 v3.0.0-beta.3 h1:7Q2I+HsIqnIEEDB+9oe7Gadpakh6ZLhXpTYz/L20vrg= +github.com/gofiber/fiber/v3 v3.0.0-beta.3/go.mod h1:kcMur0Dxqk91R7p4vxEpJfDWZ9u5IfvrtQc8Bvv/JmY= +github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co= +github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -29,6 +56,14 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= @@ -40,6 +75,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= diff --git a/rest/handlers.go b/rest/handlers.go new file mode 100644 index 0000000..8914aad --- /dev/null +++ b/rest/handlers.go @@ -0,0 +1,118 @@ +/* +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 . +*/ +package rest + +import ( + //"github.com/alecthomas/repr" + + "github.com/gofiber/fiber/v3" + "github.com/tlinden/anydb/app" + "github.com/tlinden/anydb/cfg" +) + +type SetContext struct { + Query string `json:"query" form:"query"` +} + +type ListResponse struct { + Success bool + Code int + Entries app.DbEntries +} + +type SingleResponse struct { + Success bool + Code int + Entry *app.DbEntry +} + +func RestList(c fiber.Ctx, conf *cfg.Config) error { + // FIXME: Check for tags and filter + // FIXME: https://github.com/gofiber/fiber/blob/main/docs/api/bind.md#body + /* + setcontext := new(SetContext) + if err := c.Bind().Body(setcontext); err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Unable to parse body: "+err.Error()) + } + */ + + // get list + entries, err := conf.DB.List(&app.DbAttr{}) + if err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Unable to list keys: "+err.Error()) + } + + return c.Status(fiber.StatusOK).JSON( + ListResponse{ + Success: true, + Code: fiber.StatusOK, + Entries: entries, + }, + ) +} + +func RestGet(c fiber.Ctx, conf *cfg.Config) error { + if c.Params("key") == "" { + return JsonStatus(c, fiber.StatusForbidden, + "key not provided") + } + + // get list + entry, err := conf.DB.Get(&app.DbAttr{Key: c.Params("key")}) + if err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Unable to get key: "+err.Error()) + } + if entry.Key == "" { + return JsonStatus(c, fiber.StatusForbidden, + "Key does not exist") + } + + return c.Status(fiber.StatusOK).JSON( + SingleResponse{ + Success: true, + Code: fiber.StatusOK, + Entry: entry, + }, + ) +} + +func RestDelete(c fiber.Ctx, conf *cfg.Config) error { + if c.Params("key") == "" { + return JsonStatus(c, fiber.StatusForbidden, + "key not provided") + } + + // get list + err := conf.DB.Del(&app.DbAttr{Key: c.Params("key")}) + if err != nil { + return JsonStatus(c, fiber.StatusForbidden, + "Unable to delete key: "+err.Error()) + } + + return c.Status(fiber.StatusOK).JSON( + Result{ + Success: true, + Code: fiber.StatusOK, + Message: "key deleted", + }, + ) +} + +// FIXME: add RestSet() diff --git a/rest/serve.go b/rest/serve.go new file mode 100644 index 0000000..fc17806 --- /dev/null +++ b/rest/serve.go @@ -0,0 +1,113 @@ +/* +Copyright © 2024 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 . +*/ +package rest + +import ( + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/compress" + "github.com/gofiber/fiber/v3/middleware/cors" + "github.com/gofiber/fiber/v3/middleware/logger" + "github.com/gofiber/fiber/v3/middleware/requestid" + "github.com/tlinden/anydb/cfg" +) + +// used to return to the api client +type Result struct { + Success bool `json:"success"` + Message string `json:"message"` + Code int `json:"code"` +} + +func Runserver(conf *cfg.Config, args []string) error { + // setup api server + router := SetupServer(conf) + + // public rest api routes + api := router.Group("/anydb/v1") + { + api.Get("/", func(c fiber.Ctx) error { + return RestList(c, conf) + }) + + api.Get("/:key", func(c fiber.Ctx) error { + return RestGet(c, conf) + }) + + api.Delete("/:key", func(c fiber.Ctx) error { + return RestDelete(c, conf) + }) + /* + api.Put("/", nil, func(c *fiber.Ctx) error { + return RestSet(c, conf) + }) + */ + } + + // public routes + { + router.Get("/", func(c fiber.Ctx) error { + return c.Send([]byte("Use the REST API")) + }) + } + + return router.Listen(conf.Listen) +} + +func SetupServer(conf *cfg.Config) *fiber.App { + router := fiber.New(fiber.Config{ + CaseSensitive: true, + StrictRouting: true, + Immutable: true, + ServerHeader: "anydb serve", + AppName: "anydb", + }) + + router.Use(requestid.New()) + + router.Use(logger.New(logger.Config{ + Format: "${pid} ${locals:requestid} ${status} - ${method} ${path}​\n", + })) + + router.Use(cors.New(cors.Config{ + AllowMethods: []string{"GET", "PUT", "POST", "DELETE"}, + ExposeHeaders: []string{"Content-Type", "Accept"}, + })) + + router.Use(compress.New(compress.Config{ + Level: compress.LevelBestSpeed, + })) + + return router +} + +/* +Wrapper to respond with proper json status, message and code, +shall be prepared and called by the handlers directly. +*/ +func JsonStatus(c fiber.Ctx, code int, msg string) error { + success := true + + if code != fiber.StatusOK { + success = false + } + + return c.Status(code).JSON(Result{ + Code: code, + Message: msg, + Success: success, + }) +}