mirror of
https://codeberg.org/scip/anydb.git
synced 2026-02-04 01:10:58 +01:00
Replace fiber with net.http.ServeMux (#23)
This commit is contained in:
4
anydb.1
4
anydb.1
@@ -1,4 +1,4 @@
|
|||||||
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
|
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
|
||||||
.\"
|
.\"
|
||||||
.\" Standard preamble:
|
.\" Standard preamble:
|
||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
.\"
|
.\"
|
||||||
.IX Title "ANYDB 1"
|
.IX Title "ANYDB 1"
|
||||||
.TH ANYDB 1 "2025-02-11" "1" "User Commands"
|
.TH ANYDB 1 "2025-11-03" "1" "User Commands"
|
||||||
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
|
||||||
.\" way too many mistakes in technical documents.
|
.\" way too many mistakes in technical documents.
|
||||||
.if n .ad l
|
.if n .ad l
|
||||||
|
|||||||
@@ -569,7 +569,7 @@ Some curl example calls to the API:
|
|||||||
Post a new key:
|
Post a new key:
|
||||||
curl -X PUT localhost:8787/anydb/v1/ \
|
curl -X PUT localhost:8787/anydb/v1/ \
|
||||||
-H 'Content-Type: application/json' \
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"key":"foo","val":"bar"}'
|
-d '{"key":"foo","data":"bar"}'
|
||||||
|
|
||||||
Retrieve the value:
|
Retrieve the value:
|
||||||
|
|
||||||
@@ -579,6 +579,11 @@ List all keys:
|
|||||||
|
|
||||||
curl localhost:8787/anydb/v1/
|
curl localhost:8787/anydb/v1/
|
||||||
|
|
||||||
|
Delete an entry:
|
||||||
|
|
||||||
|
curl -s -X DELETE http://localhost:8787/anydb/v1/foo
|
||||||
|
|
||||||
|
|
||||||
=head1 BUGS
|
=head1 BUGS
|
||||||
|
|
||||||
In order to report a bug, unexpected behavior, feature requests
|
In order to report a bug, unexpected behavior, feature requests
|
||||||
|
|||||||
11
app/attr.go
11
app/attr.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024-2025 Thomas von Dein
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,6 +28,7 @@ type DbAttr struct {
|
|||||||
Key string
|
Key string
|
||||||
Preview string
|
Preview string
|
||||||
Val []byte
|
Val []byte
|
||||||
|
Data string // alias
|
||||||
Args []string
|
Args []string
|
||||||
Tags []string
|
Tags []string
|
||||||
File string
|
File string
|
||||||
@@ -63,6 +64,12 @@ func (attr *DbAttr) ParseKV() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attr.SetPreview()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (attr *DbAttr) SetPreview() {
|
||||||
switch {
|
switch {
|
||||||
case attr.Binary:
|
case attr.Binary:
|
||||||
attr.Preview = "<binary-content>"
|
attr.Preview = "<binary-content>"
|
||||||
@@ -82,8 +89,6 @@ func (attr *DbAttr) ParseKV() error {
|
|||||||
attr.Preview = string(attr.Val)
|
attr.Preview = string(attr.Val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (attr *DbAttr) GetFileValue() error {
|
func (attr *DbAttr) GetFileValue() error {
|
||||||
|
|||||||
@@ -19,14 +19,18 @@ package cfg
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml"
|
|
||||||
"codeberg.org/scip/anydb/app"
|
"codeberg.org/scip/anydb/app"
|
||||||
"codeberg.org/scip/anydb/common"
|
"codeberg.org/scip/anydb/common"
|
||||||
|
"github.com/lmittmann/tint"
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
"github.com/tlinden/yadu"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string = "v0.2.6"
|
var Version string = "v0.3.0"
|
||||||
|
|
||||||
type BucketConfig struct {
|
type BucketConfig struct {
|
||||||
Encrypt bool
|
Encrypt bool
|
||||||
@@ -58,6 +62,8 @@ func (conf *Config) GetConfig(files []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf.SetLogger()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,3 +120,36 @@ func (conf *Config) ParseConfigFile(file string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conf *Config) SetLogger() {
|
||||||
|
if conf.Debug {
|
||||||
|
buildInfo, _ := debug.ReadBuildInfo()
|
||||||
|
opts := &yadu.Options{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
AddSource: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
|
||||||
|
handler := yadu.NewHandler(os.Stdout, opts)
|
||||||
|
debuglogger := slog.New(handler).With(
|
||||||
|
slog.Group("program_info",
|
||||||
|
slog.Int("pid", os.Getpid()),
|
||||||
|
slog.String("go_version", buildInfo.GoVersion),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
slog.SetDefault(debuglogger)
|
||||||
|
|
||||||
|
slog.Debug("parsed config", "conf", conf)
|
||||||
|
} else {
|
||||||
|
opts := &tint.Options{
|
||||||
|
Level: slog.LevelInfo,
|
||||||
|
AddSource: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := tint.NewHandler(os.Stderr, opts)
|
||||||
|
logger := slog.New(handler)
|
||||||
|
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024-2025 Thomas von Dein
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"codeberg.org/scip/anydb/app"
|
"codeberg.org/scip/anydb/app"
|
||||||
"codeberg.org/scip/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
"codeberg.org/scip/anydb/output"
|
"codeberg.org/scip/anydb/output"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Set(conf *cfg.Config) *cobra.Command {
|
func Set(conf *cfg.Config) *cobra.Command {
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -6,8 +6,8 @@ toolchain go1.24.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/gofiber/fiber/v2 v2.52.9
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0
|
github.com/inconshreveable/mousetrap v1.1.0
|
||||||
|
github.com/lmittmann/tint v1.1.2
|
||||||
github.com/olekukonko/tablewriter v1.1.0
|
github.com/olekukonko/tablewriter v1.1.0
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/rogpeppe/go-internal v1.14.1
|
github.com/rogpeppe/go-internal v1.14.1
|
||||||
@@ -20,10 +20,8 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/alecthomas/repr v0.5.2 // indirect
|
||||||
github.com/fatih/color v1.16.0 // indirect
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
@@ -31,9 +29,6 @@ require (
|
|||||||
github.com/olekukonko/ll v0.0.9 // indirect
|
github.com/olekukonko/ll v0.0.9 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/spf13/pflag v1.0.9 // 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
|
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.26.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
20
go.sum
20
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -7,16 +7,12 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -46,14 +42,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
|||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
|
github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
|
||||||
github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
|
github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
|
||||||
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.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/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
|
|||||||
175
rest/handlers.go
175
rest/handlers.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024-2025 Thomas von Dein
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,10 @@ package rest
|
|||||||
import (
|
import (
|
||||||
//"github.com/alecthomas/repr"
|
//"github.com/alecthomas/repr"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"codeberg.org/scip/anydb/app"
|
"codeberg.org/scip/anydb/app"
|
||||||
"codeberg.org/scip/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
@@ -40,101 +43,113 @@ type SingleResponse struct {
|
|||||||
Entry *app.DbEntry
|
Entry *app.DbEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestList(c *fiber.Ctx, conf *cfg.Config) error {
|
func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
|
||||||
attr := new(app.DbAttr)
|
attr := new(app.DbAttr)
|
||||||
|
|
||||||
if len(c.Body()) > 0 {
|
err := json.NewDecoder(req.Body).Decode(&attr)
|
||||||
if err := c.BodyParser(attr); err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
if err.Error() != `EOF` {
|
||||||
"errors": err.Error(),
|
http.Error(resp, err.Error(), http.StatusBadRequest)
|
||||||
})
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get list
|
// get list
|
||||||
entries, err := conf.DB.List(attr, attr.Fulltext)
|
entries, err := conf.DB.List(attr, attr.Fulltext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JsonStatus(c, fiber.StatusForbidden,
|
JsonStatus(resp, http.StatusForbidden, "Unable to list keys: "+err.Error())
|
||||||
"Unable to list keys: "+err.Error())
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(
|
resp.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err = json.NewEncoder(resp).Encode(
|
||||||
ListResponse{
|
ListResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
Success: true,
|
Success: true,
|
||||||
Code: fiber.StatusOK,
|
|
||||||
Entries: entries,
|
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",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestSet(c *fiber.Ctx, conf *cfg.Config) error {
|
|
||||||
attr := new(app.DbAttr)
|
|
||||||
if err := c.BodyParser(attr); err != nil {
|
|
||||||
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
|
||||||
"errors": err.Error(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
err := conf.DB.Set(attr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JsonStatus(c, fiber.StatusForbidden,
|
log.Fatal(err)
|
||||||
"Unable to set key: "+err.Error())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) {
|
||||||
|
if key == "" {
|
||||||
|
JsonStatus(resp, http.StatusForbidden, "key not provided")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(
|
// get list
|
||||||
Result{
|
entry, err := conf.DB.Get(&app.DbAttr{Key: key})
|
||||||
|
if err != nil {
|
||||||
|
JsonStatus(resp, http.StatusForbidden, "Unable to get key: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if entry.Key == "" {
|
||||||
|
JsonStatus(resp, http.StatusForbidden, "Key does not exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
err = json.NewEncoder(resp).Encode(
|
||||||
|
SingleResponse{
|
||||||
|
Code: http.StatusOK,
|
||||||
Success: true,
|
Success: true,
|
||||||
Code: fiber.StatusOK,
|
Entry: entry,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) {
|
||||||
|
if key == "" {
|
||||||
|
JsonStatus(resp, http.StatusForbidden, "key not provided")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get list
|
||||||
|
err := conf.DB.Del(&app.DbAttr{Key: key})
|
||||||
|
if err != nil {
|
||||||
|
JsonStatus(resp, http.StatusForbidden, "Unable to delete key: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonStatus(resp, http.StatusOK, "key deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestSet(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
|
||||||
|
attr := new(app.DbAttr)
|
||||||
|
|
||||||
|
err := json.NewDecoder(req.Body).Decode(&attr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(resp, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// attr.Data is a string, thus the decoder doesn't expect it to be
|
||||||
|
// base64 encoded. However, internally we need []byte, therefore
|
||||||
|
// we copy a cast to .Val. We also need to setup the .Preview
|
||||||
|
// value here.
|
||||||
|
attr.Val = []byte(attr.Data)
|
||||||
|
attr.SetPreview()
|
||||||
|
|
||||||
|
err = conf.DB.Set(attr)
|
||||||
|
if err != nil {
|
||||||
|
JsonStatus(resp, http.StatusForbidden, "Unable to set key: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonStatus(resp, http.StatusOK, "key added/updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Home(resp http.ResponseWriter) {
|
||||||
|
_, err := resp.Write([]byte("Use the REST API on " + apiprefix + "\r\n"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
rest/log.go
Normal file
98
rest/log.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
This logging middleware is based on
|
||||||
|
|
||||||
|
https://github.com/elithrar/admission-control/blob/v0.6.3/request_logger.go
|
||||||
|
|
||||||
|
by Matt Silverlock licensed under the Apache-2.0 license.
|
||||||
|
|
||||||
|
I am using slog and added a couple of small modifications.
|
||||||
|
*/
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// responseWriter is a minimal wrapper for http.ResponseWriter that allows the
|
||||||
|
// written HTTP status code to be captured for logging.
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
size int
|
||||||
|
wroteHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapResponseWriter(w http.ResponseWriter) *responseWriter {
|
||||||
|
return &responseWriter{ResponseWriter: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Status() int {
|
||||||
|
return rw.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Size() int {
|
||||||
|
return rw.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(code int) {
|
||||||
|
if rw.wroteHeader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.status = code
|
||||||
|
rw.ResponseWriter.WriteHeader(code)
|
||||||
|
rw.wroteHeader = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) Write(data []byte) (int, error) {
|
||||||
|
|
||||||
|
written, err := rw.ResponseWriter.Write(data)
|
||||||
|
rw.size += written
|
||||||
|
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggingMiddleware logs the incoming HTTP request & its duration.
|
||||||
|
func LogHandler() func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
fn := func(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
resp.WriteHeader(http.StatusInternalServerError)
|
||||||
|
slog.Info(
|
||||||
|
"internal server error",
|
||||||
|
"err", err,
|
||||||
|
"trace", string(debug.Stack()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
wrapped := wrapResponseWriter(resp)
|
||||||
|
next.ServeHTTP(wrapped, req)
|
||||||
|
|
||||||
|
header := wrapped.Header()["Content-Type"]
|
||||||
|
contenttype := ""
|
||||||
|
if header == nil {
|
||||||
|
contenttype = "text/plain"
|
||||||
|
} else {
|
||||||
|
contenttype = header[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("request",
|
||||||
|
"ip", req.RemoteAddr,
|
||||||
|
"status", wrapped.status,
|
||||||
|
"method", req.Method,
|
||||||
|
"path", req.URL.EscapedPath(),
|
||||||
|
"size", wrapped.Size(),
|
||||||
|
"content-type", contenttype,
|
||||||
|
"duration", time.Since(start),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024-2025 Thomas von Dein
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -17,10 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"encoding/json"
|
||||||
"github.com/gofiber/fiber/v2/middleware/compress"
|
"log"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"net/http"
|
||||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
|
||||||
"codeberg.org/scip/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,84 +31,62 @@ type Result struct {
|
|||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const apiprefix = `/anydb/v1/`
|
||||||
|
|
||||||
func Runserver(conf *cfg.Config, args []string) error {
|
func Runserver(conf *cfg.Config, args []string) error {
|
||||||
// setup api server
|
// setup api server
|
||||||
router := SetupServer(conf)
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
// public rest api routes
|
// just in case someone tries to access the non-api url
|
||||||
api := router.Group("/anydb/v1")
|
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
{
|
Home(w)
|
||||||
api.Get("/", func(c *fiber.Ctx) error {
|
|
||||||
return RestList(c, conf)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Post("/", func(c *fiber.Ctx) error {
|
mux.HandleFunc("GET "+apiprefix+"{$}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// same thing as above but allows to supply parameters, see app.Dbattr{}
|
RestList(w, r, conf)
|
||||||
return RestList(c, conf)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Get("/:key", func(c *fiber.Ctx) error {
|
mux.HandleFunc("POST "+apiprefix+"{$}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
return RestGet(c, conf)
|
RestList(w, r, conf)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Delete("/:key", func(c *fiber.Ctx) error {
|
mux.HandleFunc("GET "+apiprefix+"{key}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
return RestDelete(c, conf)
|
key := r.PathValue("key")
|
||||||
|
RestGet(w, r, key, conf)
|
||||||
})
|
})
|
||||||
|
|
||||||
api.Put("/", func(c *fiber.Ctx) error {
|
mux.HandleFunc("DELETE "+apiprefix+"{key}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
return RestSet(c, conf)
|
key := r.PathValue("key")
|
||||||
})
|
RestDelete(w, r, key, 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 {
|
|
||||||
// disable colors
|
|
||||||
fiber.DefaultColors = fiber.Colors{}
|
|
||||||
|
|
||||||
router := fiber.New(fiber.Config{
|
|
||||||
CaseSensitive: true,
|
|
||||||
StrictRouting: true,
|
|
||||||
Immutable: true,
|
|
||||||
ServerHeader: "anydb serve",
|
|
||||||
AppName: "anydb",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
router.Use(logger.New(logger.Config{
|
mux.HandleFunc("PUT "+apiprefix, func(w http.ResponseWriter, r *http.Request) {
|
||||||
Format: "${pid} ${ip}:${port} ${status} - ${method} ${path}\n",
|
RestSet(w, r, conf)
|
||||||
DisableColors: true,
|
})
|
||||||
}))
|
|
||||||
|
|
||||||
router.Use(cors.New(cors.Config{
|
logger := LogHandler()
|
||||||
AllowMethods: "GET,PUT,POST,DELETE",
|
|
||||||
ExposeHeaders: "Content-Type,Accept",
|
|
||||||
}))
|
|
||||||
|
|
||||||
router.Use(compress.New(compress.Config{
|
return http.ListenAndServe(conf.Listen, logger(mux))
|
||||||
Level: compress.LevelBestSpeed,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return router
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Wrapper to respond with proper json status, message and code,
|
Wrapper to respond with proper json status, message and code,
|
||||||
shall be prepared and called by the handlers directly.
|
shall be prepared and called by the handlers directly.
|
||||||
*/
|
*/
|
||||||
func JsonStatus(c *fiber.Ctx, code int, msg string) error {
|
func JsonStatus(resp http.ResponseWriter, code int, msg string) {
|
||||||
success := code == fiber.StatusOK
|
success := code == http.StatusOK
|
||||||
|
|
||||||
return c.Status(code).JSON(Result{
|
resp.Header().Set("Content-Type", "application/json")
|
||||||
|
resp.WriteHeader(code)
|
||||||
|
|
||||||
|
err := json.NewEncoder(resp).Encode(
|
||||||
|
Result{
|
||||||
Code: code,
|
Code: code,
|
||||||
Message: msg,
|
Message: msg,
|
||||||
Success: success,
|
Success: success,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user