mirror of
https://codeberg.org/scip/anydb.git
synced 2026-02-04 17:30:57 +01:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46d641301d | |||
| d1702949a2 |
@@ -66,4 +66,4 @@ release:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/anydb/compare/{{ .PreviousTag }}...{{ .Tag }})
|
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/epuppy/compare/{{ .PreviousTag }}...{{ .Tag }})
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- go get
|
- go get
|
||||||
- go build
|
- go build
|
||||||
|
- go test
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
when:
|
when:
|
||||||
@@ -24,13 +25,3 @@ steps:
|
|||||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0
|
||||||
- golangci-lint --version
|
- golangci-lint --version
|
||||||
- golangci-lint run ./...
|
- golangci-lint run ./...
|
||||||
depends_on: [build]
|
|
||||||
|
|
||||||
test:
|
|
||||||
when:
|
|
||||||
event: [push]
|
|
||||||
image: golang:${goversion}
|
|
||||||
commands:
|
|
||||||
- go get
|
|
||||||
- go test -v -cover
|
|
||||||
depends_on: [build,linter]
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
# -*-make-*-
|
|
||||||
|
|
||||||
.PHONY: install all
|
|
||||||
|
|
||||||
tool = rpn
|
|
||||||
PREFIX = /usr/local
|
|
||||||
UID = root
|
|
||||||
GID = 0
|
|
||||||
|
|
||||||
all:
|
|
||||||
@echo "Type 'sudo make install' to install the tool."
|
|
||||||
@echo "To change prefix, type 'sudo make install PREFIX=/opt'"
|
|
||||||
|
|
||||||
install:
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc
|
|
||||||
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
|
|
||||||
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
|
|
||||||
install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/
|
|
||||||
@@ -46,11 +46,11 @@ And I wrote a very similar [tool](https://www.daemon.de/projects/dbtool/) 24 yea
|
|||||||
|
|
||||||
**anydb** can do all the things you can do with skate:
|
**anydb** can do all the things you can do with skate:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
However, there are more features than just that!
|
However, there are more features than just that!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
4
anydb.1
4
anydb.1
@@ -1,4 +1,4 @@
|
|||||||
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
|
.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
|
||||||
.\"
|
.\"
|
||||||
.\" Standard preamble:
|
.\" Standard preamble:
|
||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
.\"
|
.\"
|
||||||
.IX Title "ANYDB 1"
|
.IX Title "ANYDB 1"
|
||||||
.TH ANYDB 1 "2025-11-03" "1" "User Commands"
|
.TH ANYDB 1 "2025-02-11" "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","data":"bar"}'
|
-d '{"key":"foo","val":"bar"}'
|
||||||
|
|
||||||
Retrieve the value:
|
Retrieve the value:
|
||||||
|
|
||||||
@@ -579,11 +579,6 @@ 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-2025 Thomas von Dein
|
Copyright © 2024 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,7 +28,6 @@ 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
|
||||||
@@ -64,12 +63,6 @@ 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>"
|
||||||
@@ -89,6 +82,8 @@ func (attr *DbAttr) SetPreview() {
|
|||||||
attr.Preview = string(attr.Val)
|
attr.Preview = string(attr.Val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (attr *DbAttr) GetFileValue() error {
|
func (attr *DbAttr) GetFileValue() error {
|
||||||
|
|||||||
@@ -19,18 +19,14 @@ 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.3.0"
|
var Version string = "v0.2.6"
|
||||||
|
|
||||||
type BucketConfig struct {
|
type BucketConfig struct {
|
||||||
Encrypt bool
|
Encrypt bool
|
||||||
@@ -62,8 +58,6 @@ func (conf *Config) GetConfig(files []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conf.SetLogger()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,36 +114,3 @@ 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-2025 Thomas von Dein
|
Copyright © 2024 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,8 +20,10 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/repr v0.5.2 // indirect
|
github.com/andybalholm/brotli v1.1.1 // 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
|
||||||
@@ -29,6 +31,9 @@ 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/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
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,12 +7,16 @@ 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/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
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=
|
||||||
@@ -42,6 +46,14 @@ 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=
|
||||||
|
|||||||
131
rest/handlers.go
131
rest/handlers.go
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2024-2025 Thomas von Dein
|
Copyright © 2024 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,10 +19,7 @@ package rest
|
|||||||
import (
|
import (
|
||||||
//"github.com/alecthomas/repr"
|
//"github.com/alecthomas/repr"
|
||||||
|
|
||||||
"encoding/json"
|
"github.com/gofiber/fiber/v2"
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"codeberg.org/scip/anydb/app"
|
"codeberg.org/scip/anydb/app"
|
||||||
"codeberg.org/scip/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
@@ -43,113 +40,101 @@ type SingleResponse struct {
|
|||||||
Entry *app.DbEntry
|
Entry *app.DbEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
|
func RestList(c *fiber.Ctx, conf *cfg.Config) error {
|
||||||
attr := new(app.DbAttr)
|
attr := new(app.DbAttr)
|
||||||
|
|
||||||
err := json.NewDecoder(req.Body).Decode(&attr)
|
if len(c.Body()) > 0 {
|
||||||
if err != nil {
|
if err := c.BodyParser(attr); err != nil {
|
||||||
if err.Error() != `EOF` {
|
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
||||||
http.Error(resp, err.Error(), http.StatusBadRequest)
|
"errors": err.Error(),
|
||||||
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 {
|
||||||
JsonStatus(resp, http.StatusForbidden, "Unable to list keys: "+err.Error())
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"Unable to list keys: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Header().Set("Content-Type", "application/json")
|
return c.Status(fiber.StatusOK).JSON(
|
||||||
|
|
||||||
err = json.NewEncoder(resp).Encode(
|
|
||||||
ListResponse{
|
ListResponse{
|
||||||
Code: http.StatusOK,
|
|
||||||
Success: true,
|
Success: true,
|
||||||
|
Code: fiber.StatusOK,
|
||||||
Entries: entries,
|
Entries: entries,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) {
|
func RestGet(c *fiber.Ctx, conf *cfg.Config) error {
|
||||||
if key == "" {
|
if c.Params("key") == "" {
|
||||||
JsonStatus(resp, http.StatusForbidden, "key not provided")
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"key not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get list
|
// get list
|
||||||
entry, err := conf.DB.Get(&app.DbAttr{Key: key})
|
entry, err := conf.DB.Get(&app.DbAttr{Key: c.Params("key")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
JsonStatus(resp, http.StatusForbidden, "Unable to get key: "+err.Error())
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"Unable to get key: "+err.Error())
|
||||||
}
|
}
|
||||||
if entry.Key == "" {
|
if entry.Key == "" {
|
||||||
JsonStatus(resp, http.StatusForbidden, "Key does not exist")
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"Key does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Header().Set("Content-Type", "application/json")
|
return c.Status(fiber.StatusOK).JSON(
|
||||||
|
|
||||||
err = json.NewEncoder(resp).Encode(
|
|
||||||
SingleResponse{
|
SingleResponse{
|
||||||
Code: http.StatusOK,
|
|
||||||
Success: true,
|
Success: true,
|
||||||
|
Code: fiber.StatusOK,
|
||||||
Entry: entry,
|
Entry: entry,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) {
|
func RestDelete(c *fiber.Ctx, conf *cfg.Config) error {
|
||||||
if key == "" {
|
if c.Params("key") == "" {
|
||||||
JsonStatus(resp, http.StatusForbidden, "key not provided")
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"key not provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get list
|
// get list
|
||||||
err := conf.DB.Del(&app.DbAttr{Key: key})
|
err := conf.DB.Del(&app.DbAttr{Key: c.Params("key")})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
JsonStatus(resp, http.StatusForbidden, "Unable to delete key: "+err.Error())
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"Unable to delete key: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonStatus(resp, http.StatusOK, "key deleted")
|
return c.Status(fiber.StatusOK).JSON(
|
||||||
|
Result{
|
||||||
|
Success: true,
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Message: "key deleted",
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RestSet(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
|
func RestSet(c *fiber.Ctx, conf *cfg.Config) error {
|
||||||
attr := new(app.DbAttr)
|
attr := new(app.DbAttr)
|
||||||
|
if err := c.BodyParser(attr); err != nil {
|
||||||
|
return c.Status(fiber.StatusUnprocessableEntity).JSON(fiber.Map{
|
||||||
|
"errors": err.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
err := json.NewDecoder(req.Body).Decode(&attr)
|
}
|
||||||
|
|
||||||
|
err := conf.DB.Set(attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(resp, err.Error(), http.StatusBadRequest)
|
return JsonStatus(c, fiber.StatusForbidden,
|
||||||
return
|
"Unable to set key: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// attr.Data is a string, thus the decoder doesn't expect it to be
|
return c.Status(fiber.StatusOK).JSON(
|
||||||
// base64 encoded. However, internally we need []byte, therefore
|
Result{
|
||||||
// we copy a cast to .Val. We also need to setup the .Preview
|
Success: true,
|
||||||
// value here.
|
Code: fiber.StatusOK,
|
||||||
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
98
rest/log.go
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
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-2025 Thomas von Dein
|
Copyright © 2024 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 (
|
||||||
"encoding/json"
|
"github.com/gofiber/fiber/v2"
|
||||||
"log"
|
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||||
"net/http"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
"codeberg.org/scip/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,62 +31,84 @@ 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
|
||||||
mux := http.NewServeMux()
|
router := SetupServer(conf)
|
||||||
|
|
||||||
// just in case someone tries to access the non-api url
|
// public rest api routes
|
||||||
mux.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
|
api := router.Group("/anydb/v1")
|
||||||
Home(w)
|
{
|
||||||
|
api.Get("/", func(c *fiber.Ctx) error {
|
||||||
|
return RestList(c, conf)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("GET "+apiprefix+"{$}", func(w http.ResponseWriter, r *http.Request) {
|
api.Post("/", func(c *fiber.Ctx) error {
|
||||||
RestList(w, r, conf)
|
// same thing as above but allows to supply parameters, see app.Dbattr{}
|
||||||
|
return RestList(c, conf)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("POST "+apiprefix+"{$}", func(w http.ResponseWriter, r *http.Request) {
|
api.Get("/:key", func(c *fiber.Ctx) error {
|
||||||
RestList(w, r, conf)
|
return RestGet(c, conf)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("GET "+apiprefix+"{key}", func(w http.ResponseWriter, r *http.Request) {
|
api.Delete("/:key", func(c *fiber.Ctx) error {
|
||||||
key := r.PathValue("key")
|
return RestDelete(c, conf)
|
||||||
RestGet(w, r, key, conf)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("DELETE "+apiprefix+"{key}", func(w http.ResponseWriter, r *http.Request) {
|
api.Put("/", func(c *fiber.Ctx) error {
|
||||||
key := r.PathValue("key")
|
return RestSet(c, conf)
|
||||||
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",
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("PUT "+apiprefix, func(w http.ResponseWriter, r *http.Request) {
|
router.Use(logger.New(logger.Config{
|
||||||
RestSet(w, r, conf)
|
Format: "${pid} ${ip}:${port} ${status} - ${method} ${path}\n",
|
||||||
})
|
DisableColors: true,
|
||||||
|
}))
|
||||||
|
|
||||||
logger := LogHandler()
|
router.Use(cors.New(cors.Config{
|
||||||
|
AllowMethods: "GET,PUT,POST,DELETE",
|
||||||
|
ExposeHeaders: "Content-Type,Accept",
|
||||||
|
}))
|
||||||
|
|
||||||
return http.ListenAndServe(conf.Listen, logger(mux))
|
router.Use(compress.New(compress.Config{
|
||||||
|
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(resp http.ResponseWriter, code int, msg string) {
|
func JsonStatus(c *fiber.Ctx, code int, msg string) error {
|
||||||
success := code == http.StatusOK
|
success := code == fiber.StatusOK
|
||||||
|
|
||||||
resp.Header().Set("Content-Type", "application/json")
|
return c.Status(code).JSON(Result{
|
||||||
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