mirror of
https://codeberg.org/scip/anydb.git
synced 2026-10-08 11:14:20 +02:00
Compare commits
12 Commits
github
...
2a01c6f9a7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a01c6f9a7 | ||
|
|
497b8b3201 | ||
|
|
1d45b2c6fe | ||
|
|
5c9900d985 | ||
|
|
cafc65aea7 | ||
| 4fa42e27e4 | |||
|
|
3b37a67e02 | ||
| 5a705b0af0 | |||
| 7129f644d3 | |||
| 9cb5a4b800 | |||
| 53d69b5278 | |||
|
|
a16b9e796c |
56
.github/workflows/ci.yaml
vendored
56
.github/workflows/ci.yaml
vendored
@@ -1,56 +0,0 @@
|
|||||||
name: build-and-test
|
|
||||||
|
|
||||||
#on: [push]
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
version: ['1.24']
|
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
||||||
name: Build
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.version }}
|
|
||||||
id: go
|
|
||||||
|
|
||||||
- name: checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: build
|
|
||||||
run: go build
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
run: make test
|
|
||||||
|
|
||||||
- name: Update coverage report
|
|
||||||
uses: ncruces/go-coverage-report@main
|
|
||||||
with:
|
|
||||||
report: true
|
|
||||||
chart: true
|
|
||||||
amend: true
|
|
||||||
if: |
|
|
||||||
matrix.os == 'ubuntu-latest' &&
|
|
||||||
github.event_name == 'push'
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
golangci:
|
|
||||||
name: lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: 1.24
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v8
|
|
||||||
34
.github/workflows/pushimage.yaml
vendored
34
.github/workflows/pushimage.yaml
vendored
@@ -1,34 +0,0 @@
|
|||||||
name: build-push-image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push-image:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
|
|
||||||
with:
|
|
||||||
registry: https://ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: ghcr.io/tlinden/anydb:${{ github.ref_name}}
|
|
||||||
|
|
||||||
- name: Build and push latest Docker image
|
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: ghcr.io/tlinden/anydb:latest
|
|
||||||
87
.github/workflows/release.yaml
vendored
87
.github/workflows/release.yaml
vendored
@@ -1,87 +0,0 @@
|
|||||||
name: build-release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Build Release Assets
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: 1.24.1
|
|
||||||
|
|
||||||
- name: Build the executables
|
|
||||||
run: ./mkrel.sh anydb ${{ github.ref_name}}
|
|
||||||
|
|
||||||
- name: List the executables
|
|
||||||
run: ls -l ./releases
|
|
||||||
|
|
||||||
- name: Upload the binaries
|
|
||||||
uses: svenstaro/upload-release-action@v2
|
|
||||||
with:
|
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
tag: ${{ github.ref_name }}
|
|
||||||
file: ./releases/*
|
|
||||||
file_glob: true
|
|
||||||
|
|
||||||
- name: Build Changelog
|
|
||||||
id: github_release
|
|
||||||
uses: mikepenz/release-changelog-builder-action@v5
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
mode: "PR"
|
|
||||||
configurationJson: |
|
|
||||||
{
|
|
||||||
"template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}",
|
|
||||||
"pr_template": "- #{{TITLE}} (##{{NUMBER}}) by #{{AUTHOR}}\n#{{BODY}}",
|
|
||||||
"empty_template": "- no changes",
|
|
||||||
"categories": [
|
|
||||||
{
|
|
||||||
"title": "## New Features",
|
|
||||||
"labels": ["add", "feature"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "## Bug Fixes",
|
|
||||||
"labels": ["fix", "bug", "revert"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "## Documentation Enhancements",
|
|
||||||
"labels": ["doc"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "## Refactoring Efforts",
|
|
||||||
"labels": ["refactor"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "## Miscellaneus Changes",
|
|
||||||
"labels": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ignore_labels": [
|
|
||||||
"duplicate", "good first issue", "help wanted", "invalid", "question", "wontfix"
|
|
||||||
],
|
|
||||||
"label_extractor": [
|
|
||||||
{
|
|
||||||
"pattern": "(.) (.+)",
|
|
||||||
"target": "$1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pattern": "(.) (.+)",
|
|
||||||
"target": "$1",
|
|
||||||
"on_property": "title"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
body: ${{steps.github_release.outputs.changelog}}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
linters:
|
|
||||||
exclusions:
|
|
||||||
rules:
|
|
||||||
- linters:
|
|
||||||
- staticcheck
|
|
||||||
text: "QF1008:"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
version: "2"
|
|
||||||
linters:
|
|
||||||
exclusions:
|
|
||||||
rules:
|
|
||||||
- linters:
|
|
||||||
- staticcheck
|
|
||||||
text: "QF1008:"
|
|
||||||
97
.golangci.yml
Normal file
97
.golangci.yml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- unused
|
||||||
|
- arangolint
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bidichk
|
||||||
|
- bodyclose
|
||||||
|
- canonicalheader
|
||||||
|
- clickhouselint
|
||||||
|
- containedctx
|
||||||
|
- copyloopvar
|
||||||
|
- decorder
|
||||||
|
- dogsled
|
||||||
|
- durationcheck
|
||||||
|
- embeddedstructfieldcheck
|
||||||
|
- errchkjson
|
||||||
|
- errname
|
||||||
|
- exhaustive
|
||||||
|
- exptostd
|
||||||
|
- fatcontext
|
||||||
|
- funcorder
|
||||||
|
- ginkgolinter
|
||||||
|
- gocheckcompilerdirectives
|
||||||
|
- gochecknoinits
|
||||||
|
- gochecksumtype
|
||||||
|
- gocritic
|
||||||
|
- goheader
|
||||||
|
- gomoddirectives
|
||||||
|
- gomodguard_v2
|
||||||
|
- goprintffuncname
|
||||||
|
- gosmopolitan
|
||||||
|
- grouper
|
||||||
|
- iface
|
||||||
|
- importas
|
||||||
|
- inamedparam
|
||||||
|
- interfacebloat
|
||||||
|
- intrange
|
||||||
|
- iotamixing
|
||||||
|
- lll
|
||||||
|
- loggercheck
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- modernize
|
||||||
|
- nakedret
|
||||||
|
- nilerr
|
||||||
|
- nilnesserr
|
||||||
|
- nlreturn
|
||||||
|
- nonamedreturns
|
||||||
|
- nosprintfhostport
|
||||||
|
- perfsprint
|
||||||
|
- prealloc
|
||||||
|
- promlinter
|
||||||
|
- protogetter
|
||||||
|
- reassign
|
||||||
|
- recvcheck
|
||||||
|
- rowserrcheck
|
||||||
|
- sloglint
|
||||||
|
- spancheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- tagalign
|
||||||
|
- testableexamples
|
||||||
|
- testifylint
|
||||||
|
- testpackage
|
||||||
|
- thelper
|
||||||
|
- tparallel
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unqueryvet
|
||||||
|
- usestdlibvars
|
||||||
|
- usetesting
|
||||||
|
- varnamelen
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- wsl_v5
|
||||||
|
- zerologlint
|
||||||
|
|
||||||
|
settings:
|
||||||
|
varnamelen:
|
||||||
|
ignore-names:
|
||||||
|
- err
|
||||||
|
- wg
|
||||||
|
- mu
|
||||||
|
- ts
|
||||||
|
- to
|
||||||
|
- es
|
||||||
|
- op
|
||||||
|
- fd
|
||||||
|
- id
|
||||||
|
- fn
|
||||||
|
|
||||||
71
.goreleaser.yaml
Normal file
71
.goreleaser.yaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod tidy
|
||||||
|
|
||||||
|
gitea_urls:
|
||||||
|
api: https://codeberg.org/api/v1
|
||||||
|
download: https://codeberg.org
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
- darwin
|
||||||
|
- freebsd
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- formats: [tar.gz]
|
||||||
|
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||||
|
name_template: >-
|
||||||
|
{{ .ProjectName }}_
|
||||||
|
{{- title .Os }}_
|
||||||
|
{{- if eq .Arch "amd64" }}x86_64
|
||||||
|
{{- else if eq .Arch "386" }}i386
|
||||||
|
{{- else }}{{ .Arch }}{{ end }}
|
||||||
|
{{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }}
|
||||||
|
# use zip for windows archives
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
formats: [zip]
|
||||||
|
- goos: linux
|
||||||
|
formats: [tar.gz,binary]
|
||||||
|
files:
|
||||||
|
- src: "*.md"
|
||||||
|
strip_parent: true
|
||||||
|
- src: "docs/*"
|
||||||
|
strip_parent: true
|
||||||
|
- src: Makefile.dist
|
||||||
|
dst: Makefile
|
||||||
|
wrap_in_directory: true
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
||||||
|
groups:
|
||||||
|
- title: Improved
|
||||||
|
regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$'
|
||||||
|
order: 0
|
||||||
|
- title: Fixed
|
||||||
|
regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$'
|
||||||
|
order: 1
|
||||||
|
- title: Changed
|
||||||
|
order: 999
|
||||||
|
|
||||||
|
release:
|
||||||
|
header: "# Release Notes"
|
||||||
|
footer: >-
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/anydb/compare/{{ .PreviousTag }}...{{ .Tag }})
|
||||||
39
.woodpecker/build.yaml
Normal file
39
.woodpecker/build.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
goversion:
|
||||||
|
- 1.26
|
||||||
|
lintversion:
|
||||||
|
- v2.12.2
|
||||||
|
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: ${platform}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
build:
|
||||||
|
when:
|
||||||
|
event: [push]
|
||||||
|
image: golang:${goversion}
|
||||||
|
commands:
|
||||||
|
- go get
|
||||||
|
- go build
|
||||||
|
|
||||||
|
linter:
|
||||||
|
when:
|
||||||
|
event: [push]
|
||||||
|
image: golang:${goversion}
|
||||||
|
commands:
|
||||||
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${lintversion}
|
||||||
|
- golangci-lint --version
|
||||||
|
- golangci-lint run ./...
|
||||||
|
depends_on: [build]
|
||||||
|
|
||||||
|
test:
|
||||||
|
when:
|
||||||
|
event: [push]
|
||||||
|
image: golang:${goversion}
|
||||||
|
commands:
|
||||||
|
- go get
|
||||||
|
- go test -v -cover
|
||||||
|
depends_on: [build,linter]
|
||||||
32
.woodpecker/image.yaml
Normal file
32
.woodpecker/image.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# https://woodpecker-ci.org/plugins/docker-buildx
|
||||||
|
# enable Package unit and go to /scip/-/packages after building to link to proj
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- &repo codeberg.org/${CI_REPO_OWNER}/anydb
|
||||||
|
|
||||||
|
steps:
|
||||||
|
dryrun:
|
||||||
|
image: docker.io/woodpeckerci/plugin-docker-buildx:6.0.3
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
dry_run: true
|
||||||
|
repo: *repo
|
||||||
|
tags: latest
|
||||||
|
when:
|
||||||
|
event: [pull_request]
|
||||||
|
|
||||||
|
publish:
|
||||||
|
image: docker.io/woodpeckerci/plugin-docker-buildx:6.0.3
|
||||||
|
settings:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
repo: *repo
|
||||||
|
registry: codeberg.org
|
||||||
|
tags: latest,${CI_COMMIT_SHA:0:8},${CI_COMMIT_TAG}
|
||||||
|
username: ${CI_REPO_OWNER}
|
||||||
|
password:
|
||||||
|
from_secret: REGISTRY_TOKEN
|
||||||
|
when:
|
||||||
|
event: [tag]
|
||||||
|
branch: main
|
||||||
15
.woodpecker/release.yaml
Normal file
15
.woodpecker/release.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# build release
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
goreleaser:
|
||||||
|
image: goreleaser/goreleaser
|
||||||
|
when:
|
||||||
|
event: [tag]
|
||||||
|
environment:
|
||||||
|
GITEA_TOKEN:
|
||||||
|
from_secret: DEPLOY_TOKEN
|
||||||
|
commands:
|
||||||
|
- goreleaser release --clean --verbose
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
FROM golang:1.24-alpine as builder
|
FROM golang:1.26-alpine as builder
|
||||||
|
|
||||||
RUN apk update
|
RUN apk update
|
||||||
RUN apk upgrade
|
RUN apk upgrade
|
||||||
|
|||||||
14
Makefile
14
Makefile
@@ -51,11 +51,11 @@ endif
|
|||||||
|
|
||||||
app/dbentry.pb.go: app/dbentry.proto
|
app/dbentry.pb.go: app/dbentry.proto
|
||||||
protoc -I=. --go_out=app app/dbentry.proto
|
protoc -I=. --go_out=app app/dbentry.proto
|
||||||
mv app/github.com/tlinden/anydb/app/dbentry.pb.go app/dbentry.pb.go
|
mv app/codeberg.org/scip/anydb/app/dbentry.pb.go app/dbentry.pb.go
|
||||||
rm -rf app/github.com
|
rm -rf app/github.com
|
||||||
|
|
||||||
buildlocal:
|
buildlocal:
|
||||||
go build -ldflags "-X 'github.com/tlinden/anydb/cfg.VERSION=$(VERSION)'"
|
go build -ldflags "-X 'codeberg.org/scip/anydb/cfg.VERSION=$(VERSION)'"
|
||||||
|
|
||||||
# binaries are being built by ci workflow on tag creation
|
# binaries are being built by ci workflow on tag creation
|
||||||
release:
|
release:
|
||||||
@@ -75,7 +75,7 @@ test: clean
|
|||||||
|
|
||||||
singletest:
|
singletest:
|
||||||
@echo "Call like this: ''make singletest TEST=TestPrepareColumns MOD=lib"
|
@echo "Call like this: ''make singletest TEST=TestPrepareColumns MOD=lib"
|
||||||
ANYDB_PASSWORD=test go test -run $(TEST) github.com/tlinden/anydb/$(MOD)
|
ANYDB_PASSWORD=test go test -run $(TEST) codeberg.org/scip/anydb/$(MOD)
|
||||||
|
|
||||||
cover-report:
|
cover-report:
|
||||||
go test ./... -cover -coverprofile=coverage.out
|
go test ./... -cover -coverprofile=coverage.out
|
||||||
@@ -97,12 +97,8 @@ goupdate:
|
|||||||
go get -t -u=patch ./...
|
go get -t -u=patch ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run
|
golangci-lint run --show-stats=false
|
||||||
|
@ # gocritic check -enableAll *.go
|
||||||
# keep til ireturn
|
|
||||||
lint-full:
|
|
||||||
golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,gci,godox,goimports,ireturn,stylecheck,testpackage,mirror,nestif,revive,goerr113,gomnd
|
|
||||||
gocritic check -enableAll *.go
|
|
||||||
|
|
||||||
demo:
|
demo:
|
||||||
make -C demo demo
|
make -C demo demo
|
||||||
|
|||||||
20
Makefile.dist
Normal file
20
Makefile.dist
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*-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/
|
||||||
31
README.md
31
README.md
@@ -1,11 +1,10 @@
|
|||||||
## A personal key value store
|
## A personal key value store
|
||||||
|
|
||||||
[](https://github.com/tlinden/anydb/actions)
|
[](https://ci.codeberg.org/repos/15517)
|
||||||
[](https://github.com/tlinden/anydb/blob/master/LICENSE)
|
[](https://codeberg.org/scip/anydb/blob/master/LICENSE)
|
||||||
[](https://raw.githack.com/wiki/tlinden/anydb/coverage.html)
|
[](https://goreportcard.com/report/codeberg.org/scip/anydb)
|
||||||
[](https://goreportcard.com/report/github.com/tlinden/anydb)
|
[](https://codeberg.org/scip/anydb/releases)
|
||||||
[](https://github.com/TLINDEN/anydb/releases/latest)
|
[](https://codeberg.org/scip/anydb/raw/branch/main/anydb.pod)
|
||||||
[](https://github.com/TLINDEN/anydb/blob/master/anydb.pod)
|
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Between version 0.1.0 and version 0.2.1 deletion of keys did not work. There
|
> Between version 0.1.0 and version 0.2.1 deletion of keys did not work. There
|
||||||
@@ -14,7 +13,7 @@
|
|||||||
> upgrade to 0.2.1 and above.
|
> upgrade to 0.2.1 and above.
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Version 0.1.3 introduced a [regression](https://github.com/TLINDEN/anydb/issues/19),
|
> Version 0.1.3 introduced a [regression](https://codeberg.org/scip/anydb/issues/19),
|
||||||
> which caused the encryption feature not to work correctly anymore.
|
> which caused the encryption feature not to work correctly anymore.
|
||||||
> If you are using anydb 0.1.3, you are urgently advised to
|
> If you are using anydb 0.1.3, you are urgently advised to
|
||||||
> upgrade to 0.2.0
|
> upgrade to 0.2.0
|
||||||
@@ -47,17 +46,17 @@ 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
|
||||||
|
|
||||||
There are multiple ways to install **anydb**:
|
There are multiple ways to install **anydb**:
|
||||||
|
|
||||||
- Go to the [latest release page](https://github.com/tlinden/anydb/releases/latest),
|
- Go to the [latest release page](https://codeberg.org/scip/anydb/releases),
|
||||||
locate the binary for your operating system and platform.
|
locate the binary for your operating system and platform.
|
||||||
|
|
||||||
Download it and put it into some directory within your `$PATH` variable.
|
Download it and put it into some directory within your `$PATH` variable.
|
||||||
@@ -70,7 +69,7 @@ There are multiple ways to install **anydb**:
|
|||||||
|
|
||||||
- You can also install from source. Issue the following commands in your shell:
|
- You can also install from source. Issue the following commands in your shell:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TLINDEN/anydb.git
|
git clone https://codeberg.org/scip/anydb.git
|
||||||
cd anydb
|
cd anydb
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
@@ -78,7 +77,7 @@ There are multiple ways to install **anydb**:
|
|||||||
|
|
||||||
- Or, if you have the GO toolkit installed, just install it like this:
|
- Or, if you have the GO toolkit installed, just install it like this:
|
||||||
```shell
|
```shell
|
||||||
go install github.com/tlinden/anydb@latest
|
go install codeberg.org/scip/anydb@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
If you do not find a binary release for your platform, please don't
|
If you do not find a binary release for your platform, please don't
|
||||||
@@ -105,14 +104,14 @@ Here, we operate in a local directory `mydb`, which we'll use as HOME
|
|||||||
inside the docker container. anydb will store its database in
|
inside the docker container. anydb will store its database in
|
||||||
`mydb/.config/anydb/default.db`.
|
`mydb/.config/anydb/default.db`.
|
||||||
|
|
||||||
A list of available images is [here](https://github.com/tlinden/anydb/pkgs/container/anydb/versions?filters%5Bversion_type%5D=tagged)
|
A list of available images is [here](https://codeberg.org/scip/anydb/pkgs/container/anydb/versions?filters%5Bversion_type%5D=tagged)
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation is provided as a unix man-page. It will be
|
The documentation is provided as a unix man-page. It will be
|
||||||
automatically installed if you install from source. However, you can
|
automatically installed if you install from source. However, you can
|
||||||
[read the man-page online](https://github.com/TLINDEN/anydb/blob/master/anydb.pod)
|
[read the man-page online](https://codeberg.org/scip/anydb/blob/master/anydb.pod)
|
||||||
|
|
||||||
Or if you cloned the repository you can read it this way (perl needs
|
Or if you cloned the repository you can read it this way (perl needs
|
||||||
to be installed though): `perldoc anydb.pod`.
|
to be installed though): `perldoc anydb.pod`.
|
||||||
@@ -129,7 +128,7 @@ best way for me to forget to do something.
|
|||||||
|
|
||||||
In order to report a bug, unexpected behavior, feature requests or to
|
In order to report a bug, unexpected behavior, feature requests or to
|
||||||
submit a patch, please open an issue on github:
|
submit a patch, please open an issue on github:
|
||||||
https://github.com/TLINDEN/anydb/issues.
|
https://codeberg.org/scip/anydb/issues.
|
||||||
|
|
||||||
## Copyright and license
|
## Copyright and license
|
||||||
|
|
||||||
@@ -141,7 +140,7 @@ T.v.Dein <tom AT vondein DOT org>
|
|||||||
|
|
||||||
## Project homepage
|
## Project homepage
|
||||||
|
|
||||||
https://github.com/TLINDEN/anydb
|
https://codeberg.org/scip/anydb
|
||||||
|
|
||||||
## Copyright and License
|
## Copyright and License
|
||||||
|
|
||||||
|
|||||||
32
anydb.1
32
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,13 +133,12 @@
|
|||||||
.\" ========================================================================
|
.\" ========================================================================
|
||||||
.\"
|
.\"
|
||||||
.IX Title "ANYDB 1"
|
.IX Title "ANYDB 1"
|
||||||
.TH ANYDB 1 "2025-02-11" "1" "User Commands"
|
.TH ANYDB 1 "2026-09-01" "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
|
||||||
.nh
|
.nh
|
||||||
.SH "anydb"
|
.SH "NAME"
|
||||||
.IX Header "anydb"
|
|
||||||
anydb \- a personal key value store
|
anydb \- a personal key value store
|
||||||
.SH "SYNOPSIS"
|
.SH "SYNOPSIS"
|
||||||
.IX Header "SYNOPSIS"
|
.IX Header "SYNOPSIS"
|
||||||
@@ -346,7 +345,7 @@ subcommand, see there for more details. The options \f(CW\*(C`\-n\*(C'\fR and
|
|||||||
have the same meaning as in the list command. The \*(L"json\*(R" mode prints
|
have the same meaning as in the list command. The \*(L"json\*(R" mode prints
|
||||||
the raw \s-1JSON\s0 representation of the whole database entry. Decryption
|
the raw \s-1JSON\s0 representation of the whole database entry. Decryption
|
||||||
will only take place in \*(L"simple\*(R" and \*(L"json\*(R" mode. The \*(L"template\*(R" mode
|
will only take place in \*(L"simple\*(R" and \*(L"json\*(R" mode. The \*(L"template\*(R" mode
|
||||||
provides the most flexibily, it is detailed in the section
|
provides the most flexibility, it is detailed in the section
|
||||||
\&\s-1TEMPLATES\s0.
|
\&\s-1TEMPLATES\s0.
|
||||||
.SS "list"
|
.SS "list"
|
||||||
.IX Subsection "list"
|
.IX Subsection "list"
|
||||||
@@ -605,7 +604,7 @@ being used is the \s-1GO\s0 template language, refer to
|
|||||||
.PP
|
.PP
|
||||||
Each template operates on one or more entries, no loop construct is
|
Each template operates on one or more entries, no loop construct is
|
||||||
required, the template provided applies to every matching entry
|
required, the template provided applies to every matching entry
|
||||||
separatley.
|
separateley.
|
||||||
.PP
|
.PP
|
||||||
The following template variables can be used:
|
The following template variables can be used:
|
||||||
.IP "\fB.Key\fR \- string" 4
|
.IP "\fB.Key\fR \- string" 4
|
||||||
@@ -613,8 +612,19 @@ The following template variables can be used:
|
|||||||
.PD 0
|
.PD 0
|
||||||
.IP "\fB.Value\fR \- string" 4
|
.IP "\fB.Value\fR \- string" 4
|
||||||
.IX Item ".Value - string"
|
.IX Item ".Value - string"
|
||||||
|
.PD
|
||||||
|
To be able to use the value in a template, you'll need to add the
|
||||||
|
\&\f(CW\*(C`\-s\*(C'\fR flag so that values are included in output data
|
||||||
|
stream. Otherwise you can only use \f(CW\*(C`.Preview\*(C'\fR.
|
||||||
|
.Sp
|
||||||
|
Be careful if you use \f(CW\*(C`\-s\*(C'\fR and \f(CW\*(C`.Value\*(C'\fR though: if a value is a
|
||||||
|
multiline string, it may clutter your output.
|
||||||
|
.IP "\fB.Preview\fR string" 4
|
||||||
|
.IX Item ".Preview string"
|
||||||
|
First line of entry value (if it is a string).
|
||||||
.IP "\fB.Bin\fR \- []byte" 4
|
.IP "\fB.Bin\fR \- []byte" 4
|
||||||
.IX Item ".Bin - []byte"
|
.IX Item ".Bin - []byte"
|
||||||
|
.PD 0
|
||||||
.IP "\fB.Created\fR \- timestamp.Time" 4
|
.IP "\fB.Created\fR \- timestamp.Time" 4
|
||||||
.IX Item ".Created - timestamp.Time"
|
.IX Item ".Created - timestamp.Time"
|
||||||
.PD
|
.PD
|
||||||
@@ -729,7 +739,7 @@ Some curl example calls to the \s-1API:\s0
|
|||||||
Post a new key:
|
Post a new key:
|
||||||
curl \-X \s-1PUT\s0 localhost:8787/anydb/v1/ \e
|
curl \-X \s-1PUT\s0 localhost:8787/anydb/v1/ \e
|
||||||
\-H 'Content\-Type: application/json' \e
|
\-H 'Content\-Type: application/json' \e
|
||||||
\-d '{\*(L"key\*(R":\*(L"foo\*(R",\*(L"val\*(R":\*(L"bar\*(R"}'
|
\-d '{\*(L"key\*(R":\*(L"foo\*(R",\*(L"data\*(R":\*(L"bar\*(R"}'
|
||||||
.PP
|
.PP
|
||||||
Retrieve the value:
|
Retrieve the value:
|
||||||
.PP
|
.PP
|
||||||
@@ -742,11 +752,17 @@ List all keys:
|
|||||||
.Vb 1
|
.Vb 1
|
||||||
\& curl localhost:8787/anydb/v1/
|
\& curl localhost:8787/anydb/v1/
|
||||||
.Ve
|
.Ve
|
||||||
|
.PP
|
||||||
|
Delete an entry:
|
||||||
|
.PP
|
||||||
|
.Vb 1
|
||||||
|
\& curl \-s \-X DELETE http://localhost:8787/anydb/v1/foo
|
||||||
|
.Ve
|
||||||
.SH "BUGS"
|
.SH "BUGS"
|
||||||
.IX Header "BUGS"
|
.IX Header "BUGS"
|
||||||
In order to report a bug, unexpected behavior, feature requests
|
In order to report a bug, unexpected behavior, feature requests
|
||||||
or to submit a patch, please open an issue on github:
|
or to submit a patch, please open an issue on github:
|
||||||
<https://github.com/TLINDEN/anydb/issues>.
|
<https://codeberg.org/scip/anydb/issues>.
|
||||||
.PP
|
.PP
|
||||||
Please repeat the failing command with debugging enabled \f(CW\*(C`\-d\*(C'\fR and
|
Please repeat the failing command with debugging enabled \f(CW\*(C`\-d\*(C'\fR and
|
||||||
include the output in the issue.
|
include the output in the issue.
|
||||||
|
|||||||
26
anydb.pod
26
anydb.pod
@@ -1,4 +1,4 @@
|
|||||||
=head1 anydb
|
=head1 NAME
|
||||||
|
|
||||||
anydb - a personal key value store
|
anydb - a personal key value store
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ subcommand, see there for more details. The options C<-n> and C<-N>
|
|||||||
have the same meaning as in the list command. The "json" mode prints
|
have the same meaning as in the list command. The "json" mode prints
|
||||||
the raw JSON representation of the whole database entry. Decryption
|
the raw JSON representation of the whole database entry. Decryption
|
||||||
will only take place in "simple" and "json" mode. The "template" mode
|
will only take place in "simple" and "json" mode. The "template" mode
|
||||||
provides the most flexibily, it is detailed in the section
|
provides the most flexibility, it is detailed in the section
|
||||||
L<TEMPLATES>.
|
L<TEMPLATES>.
|
||||||
|
|
||||||
=head2 list
|
=head2 list
|
||||||
@@ -442,7 +442,7 @@ L<https://pkg.go.dev/text/template> for details.
|
|||||||
|
|
||||||
Each template operates on one or more entries, no loop construct is
|
Each template operates on one or more entries, no loop construct is
|
||||||
required, the template provided applies to every matching entry
|
required, the template provided applies to every matching entry
|
||||||
separatley.
|
separateley.
|
||||||
|
|
||||||
The following template variables can be used:
|
The following template variables can be used:
|
||||||
|
|
||||||
@@ -452,6 +452,17 @@ The following template variables can be used:
|
|||||||
|
|
||||||
=item B<.Value> - string
|
=item B<.Value> - string
|
||||||
|
|
||||||
|
To be able to use the value in a template, you'll need to add the
|
||||||
|
C<-s> flag so that values are included in output data
|
||||||
|
stream. Otherwise you can only use C<.Preview>.
|
||||||
|
|
||||||
|
Be careful if you use C<-s> and C<.Value> though: if a value is a
|
||||||
|
multiline string, it may clutter your output.
|
||||||
|
|
||||||
|
=item B<.Preview> string
|
||||||
|
|
||||||
|
First line of entry value (if it is a string).
|
||||||
|
|
||||||
=item B<.Bin> - []byte
|
=item B<.Bin> - []byte
|
||||||
|
|
||||||
=item B<.Created> - timestamp.Time
|
=item B<.Created> - timestamp.Time
|
||||||
@@ -569,7 +580,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,11 +590,16 @@ 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
|
||||||
or to submit a patch, please open an issue on github:
|
or to submit a patch, please open an issue on github:
|
||||||
L<https://github.com/TLINDEN/anydb/issues>.
|
L<https://codeberg.org/scip/anydb/issues>.
|
||||||
|
|
||||||
Please repeat the failing command with debugging enabled C<-d> and
|
Please repeat the failing command with debugging enabled C<-d> and
|
||||||
include the output in the issue.
|
include the output in the issue.
|
||||||
|
|||||||
27
app/attr.go
27
app/attr.go
@@ -1,5 +1,7 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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
|
||||||
@@ -14,7 +16,6 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -28,6 +29,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 +65,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 +90,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 {
|
||||||
@@ -120,17 +126,24 @@ func (attr *DbAttr) GetFileValue() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// read from console stdin
|
// read from console stdin
|
||||||
var input string
|
var (
|
||||||
var data string
|
input string
|
||||||
|
data string
|
||||||
|
buf strings.Builder
|
||||||
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, err := fmt.Scanln(&input)
|
_, err := fmt.Scanln(&input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
data += input + "\n"
|
|
||||||
|
buf.WriteString(input)
|
||||||
|
buf.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data += buf.String()
|
||||||
|
|
||||||
attr.Val = []byte(data)
|
attr.Val = []byte(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024 Thomas von Dein
|
||||||
|
|
||||||
@@ -14,7 +16,6 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -35,7 +36,7 @@ const (
|
|||||||
ArgonParallel uint8 = 2
|
ArgonParallel uint8 = 2
|
||||||
ArgonSaltLen int = 16
|
ArgonSaltLen int = 16
|
||||||
ArgonKeyLen uint32 = 32
|
ArgonKeyLen uint32 = 32
|
||||||
B64SaltLen int = 16 //22
|
B64SaltLen int = 16 // 22
|
||||||
)
|
)
|
||||||
|
|
||||||
type Key struct {
|
type Key struct {
|
||||||
@@ -47,6 +48,8 @@ type Key struct {
|
|||||||
// password
|
// password
|
||||||
func AskForPassword() ([]byte, error) {
|
func AskForPassword() ([]byte, error) {
|
||||||
fmt.Fprint(os.Stderr, "Password: ")
|
fmt.Fprint(os.Stderr, "Password: ")
|
||||||
|
|
||||||
|
// nolint: unconvert
|
||||||
pass, err := term.ReadPassword(int(syscall.Stdin))
|
pass, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read password: %w", err)
|
return nil, fmt.Errorf("failed to read password: %w", err)
|
||||||
@@ -57,6 +60,8 @@ func AskForPassword() ([]byte, error) {
|
|||||||
return pass, nil
|
return pass, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeriveKey derives a key from password.
|
||||||
|
//
|
||||||
// We're using the Argon2id key derivation algorithm to derive a
|
// We're using the Argon2id key derivation algorithm to derive a
|
||||||
// secure key from the given password. This is important, because
|
// secure key from the given password. This is important, because
|
||||||
// users might use unsecure passwords. The resulting encrypted data
|
// users might use unsecure passwords. The resulting encrypted data
|
||||||
@@ -78,7 +83,7 @@ func DeriveKey(password []byte, salt []byte) (*Key, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hash := argon2.IDKey(
|
hash := argon2.IDKey(
|
||||||
[]byte(password), salt,
|
password, salt,
|
||||||
ArgonIter,
|
ArgonIter,
|
||||||
ArgonMem,
|
ArgonMem,
|
||||||
ArgonParallel,
|
ArgonParallel,
|
||||||
@@ -95,6 +100,7 @@ func DeriveKey(password []byte, salt []byte) (*Key, error) {
|
|||||||
// Retrieve a random chunk of given size
|
// Retrieve a random chunk of given size
|
||||||
func GetRandom(size int, capacity int) ([]byte, error) {
|
func GetRandom(size int, capacity int) ([]byte, error) {
|
||||||
buf := make([]byte, size, capacity)
|
buf := make([]byte, size, capacity)
|
||||||
|
|
||||||
_, err := rand.Read(buf)
|
_, err := rand.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve random bytes: %w", err)
|
return nil, fmt.Errorf("failed to retrieve random bytes: %w", err)
|
||||||
@@ -103,7 +109,7 @@ func GetRandom(size int, capacity int) ([]byte, error) {
|
|||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt clear text given in attr using ChaCha20 and auhtenticate
|
// Encrypt clear text given in attr using ChaCha20 and authenticate
|
||||||
// using the mac Poly1305. The cipher text will be put into attr, thus
|
// using the mac Poly1305. The cipher text will be put into attr, thus
|
||||||
// modifying it.
|
// modifying it.
|
||||||
//
|
//
|
||||||
@@ -143,7 +149,7 @@ func Encrypt(pass []byte, attr *DbAttr) error {
|
|||||||
// Do the reverse
|
// Do the reverse
|
||||||
func Decrypt(pass []byte, cipherb []byte) ([]byte, error) {
|
func Decrypt(pass []byte, cipherb []byte) ([]byte, error) {
|
||||||
if len(cipherb) < B64SaltLen {
|
if len(cipherb) < B64SaltLen {
|
||||||
return nil, fmt.Errorf("encrypted cipher block too small")
|
return nil, errors.New("encrypted cipher block too small")
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := DeriveKey(pass, cipherb[0:B64SaltLen])
|
key, err := DeriveKey(pass, cipherb[0:B64SaltLen])
|
||||||
|
|||||||
192
app/db.go
192
app/db.go
@@ -25,10 +25,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
common "github.com/tlinden/anydb/common"
|
common "codeberg.org/scip/anydb/common"
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
@@ -64,7 +65,7 @@ type DbTag struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (entry *DbEntry) Taglist() string {
|
func (entry *DbEntry) Taglist() string {
|
||||||
return strings.Join(entry.Tags, ",")
|
return strings.Join(entry.GetTags(), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
const BucketData string = "data"
|
const BucketData string = "data"
|
||||||
@@ -91,6 +92,14 @@ func New(file string, bucket string, debug bool) (*DB, error) {
|
|||||||
return &DB{Debug: debug, Dbfile: file, Bucket: bucket}, nil
|
return &DB{Debug: debug, Dbfile: file, Bucket: bucket}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) Exists() error {
|
||||||
|
if !common.FileExists(db.Dbfile) {
|
||||||
|
return fmt.Errorf("db file %s doesn't exist", db.Dbfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) Open() error {
|
func (db *DB) Open() error {
|
||||||
slog.Debug("opening DB", "dbfile", db.Dbfile)
|
slog.Debug("opening DB", "dbfile", db.Dbfile)
|
||||||
|
|
||||||
@@ -113,6 +122,7 @@ func (db *DB) Open() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db.DB = b
|
db.DB = b
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +138,10 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
var entries DbEntries
|
var (
|
||||||
var filter *regexp.Regexp
|
entries DbEntries
|
||||||
|
filter *regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
if len(attr.Args) > 0 {
|
if len(attr.Args) > 0 {
|
||||||
// via cli
|
// via cli
|
||||||
@@ -158,7 +170,7 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
|
|||||||
|
|
||||||
databucket := root.Bucket([]byte("data"))
|
databucket := root.Bucket([]byte("data"))
|
||||||
if databucket == nil {
|
if databucket == nil {
|
||||||
return fmt.Errorf("failed to retrieve data sub bucket")
|
return errors.New("failed to retrieve data sub bucket")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bucket.ForEach(func(key, pbentry []byte) error {
|
err := bucket.ForEach(func(key, pbentry []byte) error {
|
||||||
@@ -169,7 +181,7 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
|
|||||||
|
|
||||||
if fulltext {
|
if fulltext {
|
||||||
// avoid crash due to access fault
|
// avoid crash due to access fault
|
||||||
value := databucket.Get([]byte(entry.Key)) // empty is ok
|
value := databucket.Get([]byte(entry.GetKey())) // empty is ok
|
||||||
vc := make([]byte, len(value))
|
vc := make([]byte, len(value))
|
||||||
copy(vc, value)
|
copy(vc, value)
|
||||||
entry.Value = string(vc)
|
entry.Value = string(vc)
|
||||||
@@ -179,26 +191,21 @@ func (db *DB) List(attr *DbAttr, fulltext bool) (DbEntries, error) {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case filter != nil:
|
case filter != nil:
|
||||||
if filter.MatchString(entry.Key) ||
|
if filter.MatchString(entry.GetKey()) ||
|
||||||
filter.MatchString(strings.Join(entry.Tags, " ")) {
|
filter.MatchString(strings.Join(entry.GetTags(), " ")) {
|
||||||
include = true
|
include = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !entry.Binary && !include && fulltext {
|
if !entry.GetBinary() && !include && fulltext {
|
||||||
if filter.MatchString(string(entry.Value)) {
|
if filter.MatchString(entry.GetValue()) {
|
||||||
include = true
|
include = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case len(attr.Tags) > 0:
|
case len(attr.Tags) > 0:
|
||||||
for _, search := range attr.Tags {
|
for _, search := range attr.Tags {
|
||||||
for _, tag := range entry.Tags {
|
if slices.Contains(entry.GetTags(), search) {
|
||||||
if tag == search {
|
include = true
|
||||||
include = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if include {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +246,7 @@ func (db *DB) Set(attr *DbAttr) error {
|
|||||||
// any tags. if so, we initialize our update struct with these
|
// any tags. if so, we initialize our update struct with these
|
||||||
// tags unless it has new tags configured.
|
// tags unless it has new tags configured.
|
||||||
slog.Debug("+++ GET")
|
slog.Debug("+++ GET")
|
||||||
|
|
||||||
oldentry, err := db.txGet(attr)
|
oldentry, err := db.txGet(attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !strings.Contains(err.Error(), "no such key") {
|
if !strings.Contains(err.Error(), "no such key") {
|
||||||
@@ -247,9 +255,9 @@ func (db *DB) Set(attr *DbAttr) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oldentry != nil {
|
if oldentry != nil {
|
||||||
if len(oldentry.Tags) > 0 && len(entry.Tags) == 0 {
|
if len(oldentry.GetTags()) > 0 && len(entry.GetTags()) == 0 {
|
||||||
// initialize update entry with tags from old entry
|
// initialize update entry with tags from old entry
|
||||||
entry.Tags = oldentry.Tags
|
entry.Tags = oldentry.GetTags()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +269,7 @@ func (db *DB) Set(attr *DbAttr) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("+++ UPDATE")
|
slog.Debug("+++ UPDATE")
|
||||||
|
|
||||||
err = db.DB.Update(func(tx *bolt.Tx) error {
|
err = db.DB.Update(func(tx *bolt.Tx) error {
|
||||||
// create root bucket
|
// create root bucket
|
||||||
root, err := tx.CreateBucketIfNotExists([]byte(db.Bucket))
|
root, err := tx.CreateBucketIfNotExists([]byte(db.Bucket))
|
||||||
@@ -277,7 +286,7 @@ func (db *DB) Set(attr *DbAttr) error {
|
|||||||
slog.Debug("opened/created buckets", "root", root, "data", bucket)
|
slog.Debug("opened/created buckets", "root", root, "data", bucket)
|
||||||
|
|
||||||
// write meta data
|
// write meta data
|
||||||
err = bucket.Put([]byte(entry.Key), []byte(pbentry))
|
err = bucket.Put([]byte(entry.GetKey()), pbentry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert data: %w", err)
|
return fmt.Errorf("failed to insert data: %w", err)
|
||||||
}
|
}
|
||||||
@@ -289,14 +298,13 @@ func (db *DB) Set(attr *DbAttr) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write value
|
// write value
|
||||||
err = databucket.Put([]byte(entry.Key), attr.Val)
|
err = databucket.Put([]byte(entry.GetKey()), attr.Val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert data: %w", err)
|
return fmt.Errorf("failed to insert data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -304,67 +312,6 @@ func (db *DB) Set(attr *DbAttr) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal DB getter, assumes db.DB has already been
|
|
||||||
// opened successfully. Do NOT call this w/o valid
|
|
||||||
// DB handle!
|
|
||||||
func (db *DB) txGet(attr *DbAttr) (*DbEntry, error) {
|
|
||||||
entry := DbEntry{}
|
|
||||||
|
|
||||||
err := db.DB.View(func(tx *bolt.Tx) error {
|
|
||||||
// root bucket
|
|
||||||
root := tx.Bucket([]byte(db.Bucket))
|
|
||||||
if root == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get meta sub bucket
|
|
||||||
bucket := root.Bucket([]byte("meta"))
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Debug("opened buckets", "root", root, "data", bucket)
|
|
||||||
|
|
||||||
// retrieve meta data
|
|
||||||
pbentry := bucket.Get([]byte(attr.Key))
|
|
||||||
if pbentry == nil {
|
|
||||||
return fmt.Errorf("no such key: %s", attr.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// put into struct
|
|
||||||
if err := proto.Unmarshal(pbentry, &entry); err != nil {
|
|
||||||
return fmt.Errorf("failed to unmarshal from protobuf: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get data sub bucket
|
|
||||||
databucket := root.Bucket([]byte("data"))
|
|
||||||
if databucket == nil {
|
|
||||||
return fmt.Errorf("failed to retrieve data sub bucket")
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve actual data value
|
|
||||||
value := databucket.Get([]byte(attr.Key))
|
|
||||||
if len(value) == 0 {
|
|
||||||
return fmt.Errorf("no such key: %s", attr.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need to make a copy of it, otherwise we'll get an
|
|
||||||
// "unexpected fault address" error
|
|
||||||
vc := make([]byte, len(value))
|
|
||||||
copy(vc, value)
|
|
||||||
|
|
||||||
entry.Value = string(vc)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &entry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
|
func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
|
||||||
if err := db.Open(); err != nil {
|
if err := db.Open(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -372,7 +319,6 @@ func (db *DB) Get(attr *DbAttr) (*DbEntry, error) {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
entry, err := db.txGet(attr)
|
entry, err := db.txGet(attr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read from DB: %w", err)
|
return nil, fmt.Errorf("failed to read from DB: %w", err)
|
||||||
}
|
}
|
||||||
@@ -418,10 +364,11 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var entries DbEntries
|
var entries DbEntries
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
newfile := db.Dbfile + now.Format("-02.01.2006T03:04.05")
|
newfile := db.Dbfile + now.Format("-02.01.2006T03:04.05")
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(attr.Val), &entries); err != nil {
|
if err := json.Unmarshal(attr.Val, &entries); err != nil {
|
||||||
return "", cleanError(newfile, fmt.Errorf("failed to unmarshal json: %w", err))
|
return "", cleanError(newfile, fmt.Errorf("failed to unmarshal json: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +378,6 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to rename file %s to %s: %w", db.Dbfile, newfile, err)
|
return "", fmt.Errorf("failed to rename file %s to %s: %w", db.Dbfile, newfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// should now be a new db file
|
// should now be a new db file
|
||||||
@@ -462,7 +408,7 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write meta data
|
// write meta data
|
||||||
err = bucket.Put([]byte(entry.Key), []byte(pbentry))
|
err = bucket.Put([]byte(entry.GetKey()), pbentry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert data into DB: %w", err)
|
return fmt.Errorf("failed to insert data into DB: %w", err)
|
||||||
}
|
}
|
||||||
@@ -474,7 +420,7 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write value
|
// write value
|
||||||
err = databucket.Put([]byte(entry.Key), []byte(entry.Value))
|
err = databucket.Put([]byte(entry.GetKey()), []byte(entry.GetValue()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to insert data: %w", err)
|
return fmt.Errorf("failed to insert data: %w", err)
|
||||||
}
|
}
|
||||||
@@ -482,7 +428,6 @@ func (db *DB) Import(attr *DbAttr) (string, error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", cleanError(newfile, err)
|
return "", cleanError(newfile, err)
|
||||||
}
|
}
|
||||||
@@ -520,16 +465,14 @@ func (db *DB) Info() (*DbInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info.Buckets = append(info.Buckets, binfo)
|
info.Buckets = append(info.Buckets, binfo)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read from DB: %w", err)
|
return fmt.Errorf("failed to read from DB: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return info, err
|
return info, err
|
||||||
@@ -559,7 +502,7 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) {
|
|||||||
// get data sub bucket
|
// get data sub bucket
|
||||||
databucket := root.Bucket([]byte("data"))
|
databucket := root.Bucket([]byte("data"))
|
||||||
if databucket == nil {
|
if databucket == nil {
|
||||||
return fmt.Errorf("failed to retrieve data sub bucket")
|
return errors.New("failed to retrieve data sub bucket")
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("opened buckets", "root", root, "data", bucket)
|
slog.Debug("opened buckets", "root", root, "data", bucket)
|
||||||
@@ -572,7 +515,7 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retrieve the value from the data sub bucket
|
// retrieve the value from the data sub bucket
|
||||||
value := databucket.Get([]byte(entry.Key))
|
value := databucket.Get([]byte(entry.GetKey()))
|
||||||
|
|
||||||
// we need to make a copy of it, otherwise we'll get an
|
// we need to make a copy of it, otherwise we'll get an
|
||||||
// "unexpected fault address" error
|
// "unexpected fault address" error
|
||||||
@@ -587,5 +530,66 @@ func (db *DB) Getall(attr *DbAttr) (DbEntries, error) {
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internal DB getter, assumes db.DB has already been
|
||||||
|
// opened successfully. Do NOT call this w/o valid
|
||||||
|
// DB handle!
|
||||||
|
func (db *DB) txGet(attr *DbAttr) (*DbEntry, error) {
|
||||||
|
entry := DbEntry{}
|
||||||
|
|
||||||
|
err := db.DB.View(func(tx *bolt.Tx) error {
|
||||||
|
// root bucket
|
||||||
|
root := tx.Bucket([]byte(db.Bucket))
|
||||||
|
if root == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get meta sub bucket
|
||||||
|
bucket := root.Bucket([]byte("meta"))
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("opened buckets", "root", root, "data", bucket)
|
||||||
|
|
||||||
|
// retrieve meta data
|
||||||
|
pbentry := bucket.Get([]byte(attr.Key))
|
||||||
|
if pbentry == nil {
|
||||||
|
return fmt.Errorf("no such key: %s", attr.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// put into struct
|
||||||
|
if err := proto.Unmarshal(pbentry, &entry); err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal from protobuf: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get data sub bucket
|
||||||
|
databucket := root.Bucket([]byte("data"))
|
||||||
|
if databucket == nil {
|
||||||
|
return errors.New("failed to retrieve data sub bucket")
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve actual data value
|
||||||
|
value := databucket.Get([]byte(attr.Key))
|
||||||
|
if len(value) == 0 {
|
||||||
|
return fmt.Errorf("no such key: %s", attr.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to make a copy of it, otherwise we'll get an
|
||||||
|
// "unexpected fault address" error
|
||||||
|
vc := make([]byte, len(value))
|
||||||
|
copy(vc, value)
|
||||||
|
|
||||||
|
entry.Value = string(vc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entry, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package app;
|
|||||||
|
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
option go_package = "github.com/tlinden/anydb/app";
|
option go_package = "codeberg.org/scip/anydb/app";
|
||||||
|
|
||||||
message DbEntry {
|
message DbEntry {
|
||||||
string Id = 1;
|
string Id = 1;
|
||||||
|
|||||||
16
app/io.go
16
app/io.go
@@ -1,3 +1,5 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024 Thomas von Dein
|
||||||
|
|
||||||
@@ -14,18 +16,24 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package app
|
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func cleanError(file string, err error) error {
|
func cleanError(file string, err error) error {
|
||||||
// remove given [backup] file and forward the given error
|
// remove given [backup] file and forward the given error
|
||||||
return os.Remove(file)
|
oserr := os.Remove(file)
|
||||||
|
if oserr != nil {
|
||||||
|
return errors.Join(err, oserr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileExists(filename string) bool {
|
func fileExists(filename string) bool {
|
||||||
info, err := os.Stat(filename)
|
info, err := os.Stat(filename)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// return false on any error
|
// return false on any error
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
package cfg
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright © 2025 Thomas von Dein
|
Copyright © 2025-2026 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
|
||||||
@@ -14,19 +16,22 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package cfg
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/pelletier/go-toml"
|
"codeberg.org/scip/anydb/app"
|
||||||
"github.com/tlinden/anydb/app"
|
"codeberg.org/scip/anydb/common"
|
||||||
"github.com/tlinden/anydb/common"
|
"github.com/lmittmann/tint"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/tlinden/yadu"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string = "v0.2.6"
|
var Version string = "v0.3.2"
|
||||||
|
|
||||||
type BucketConfig struct {
|
type BucketConfig struct {
|
||||||
Encrypt bool
|
Encrypt bool
|
||||||
@@ -58,6 +63,8 @@ func (conf *Config) GetConfig(files []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf.SetLogger()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +84,7 @@ func (conf *Config) ParseConfigFile(file string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add := Config{}
|
add := Config{}
|
||||||
|
|
||||||
err = toml.Unmarshal(data, &add)
|
err = toml.Unmarshal(data, &add)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshall toml: %w", err)
|
return fmt.Errorf("failed to unmarshall toml: %w", err)
|
||||||
@@ -114,3 +122,35 @@ 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := slog.New(tint.NewTextHandler(os.Stderr, opts))
|
||||||
|
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
24
cmd/anydb.go
24
cmd/anydb.go
@@ -1,7 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
var manpage = `
|
var manpage = `
|
||||||
anydb
|
NAME
|
||||||
anydb - a personal key value store
|
anydb - a personal key value store
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
@@ -179,7 +179,7 @@ SUBCOMMANDS
|
|||||||
the same meaning as in the list command. The "json" mode prints the raw
|
the same meaning as in the list command. The "json" mode prints the raw
|
||||||
JSON representation of the whole database entry. Decryption will only
|
JSON representation of the whole database entry. Decryption will only
|
||||||
take place in "simple" and "json" mode. The "template" mode provides the
|
take place in "simple" and "json" mode. The "template" mode provides the
|
||||||
most flexibily, it is detailed in the section TEMPLATES.
|
most flexibility, it is detailed in the section TEMPLATES.
|
||||||
|
|
||||||
list
|
list
|
||||||
The list subcommand displays a list of all database entries.
|
The list subcommand displays a list of all database entries.
|
||||||
@@ -407,12 +407,22 @@ TEMPLATES
|
|||||||
|
|
||||||
Each template operates on one or more entries, no loop construct is
|
Each template operates on one or more entries, no loop construct is
|
||||||
required, the template provided applies to every matching entry
|
required, the template provided applies to every matching entry
|
||||||
separatley.
|
separateley.
|
||||||
|
|
||||||
The following template variables can be used:
|
The following template variables can be used:
|
||||||
|
|
||||||
.Key - string
|
.Key - string
|
||||||
.Value - string
|
.Value - string
|
||||||
|
To be able to use the value in a template, you'll need to add the
|
||||||
|
"-s" flag so that values are included in output data stream.
|
||||||
|
Otherwise you can only use ".Preview".
|
||||||
|
|
||||||
|
Be careful if you use "-s" and ".Value" though: if a value is a
|
||||||
|
multiline string, it may clutter your output.
|
||||||
|
|
||||||
|
.Preview string
|
||||||
|
First line of entry value (if it is a string).
|
||||||
|
|
||||||
.Bin - []byte
|
.Bin - []byte
|
||||||
.Created - timestamp.Time
|
.Created - timestamp.Time
|
||||||
To retrieve a string representation of the timestamp, use
|
To retrieve a string representation of the timestamp, use
|
||||||
@@ -504,7 +514,7 @@ REST API
|
|||||||
Some curl example calls to the API:
|
Some curl example calls to the API:
|
||||||
|
|
||||||
Post a new key: curl -X PUT localhost:8787/anydb/v1/ \ -H 'Content-Type:
|
Post a new key: curl -X PUT localhost:8787/anydb/v1/ \ -H 'Content-Type:
|
||||||
application/json' \ -d '{"key":"foo","val":"bar"}'
|
application/json' \ -d '{"key":"foo","data":"bar"}'
|
||||||
|
|
||||||
Retrieve the value:
|
Retrieve the value:
|
||||||
|
|
||||||
@@ -514,10 +524,14 @@ REST API
|
|||||||
|
|
||||||
curl localhost:8787/anydb/v1/
|
curl localhost:8787/anydb/v1/
|
||||||
|
|
||||||
|
Delete an entry:
|
||||||
|
|
||||||
|
curl -s -X DELETE http://localhost:8787/anydb/v1/foo
|
||||||
|
|
||||||
BUGS
|
BUGS
|
||||||
In order to report a bug, unexpected behavior, feature requests or to
|
In order to report a bug, unexpected behavior, feature requests or to
|
||||||
submit a patch, please open an issue on github:
|
submit a patch, please open an issue on github:
|
||||||
<https://github.com/TLINDEN/anydb/issues>.
|
<https://codeberg.org/scip/anydb/issues>.
|
||||||
|
|
||||||
Please repeat the failing command with debugging enabled "-d" and
|
Please repeat the failing command with debugging enabled "-d" and
|
||||||
include the output in the issue.
|
include the output in the issue.
|
||||||
|
|||||||
28
cmd/crud.go
28
cmd/crud.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
|
||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/app"
|
||||||
|
"codeberg.org/scip/anydb/cfg"
|
||||||
|
"codeberg.org/scip/anydb/output"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tlinden/anydb/app"
|
|
||||||
"github.com/tlinden/anydb/cfg"
|
|
||||||
"github.com/tlinden/anydb/output"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Set(conf *cfg.Config) *cobra.Command {
|
func Set(conf *cfg.Config) *cobra.Command {
|
||||||
@@ -107,24 +107,28 @@ func Get(conf *cfg.Config) *cobra.Command {
|
|||||||
attr.Key = args[0]
|
attr.Key = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := conf.DB.Exists(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
entry, err := conf.DB.Get(&attr)
|
entry, err := conf.DB.Get(&attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.Encrypted {
|
if entry.GetEncrypted() {
|
||||||
pass, err := getPassword()
|
pass, err := getPassword()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
clear, err := app.Decrypt(pass, []byte(entry.Value))
|
clear, err := app.Decrypt(pass, []byte(entry.GetValue()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.Value = string(clear)
|
entry.Value = string(clear)
|
||||||
entry.Size = uint64(len(entry.Value))
|
entry.Size = uint64(len(entry.GetValue()))
|
||||||
entry.Encrypted = false
|
entry.Encrypted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +137,8 @@ func Get(conf *cfg.Config) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVarP(&attr.File, "output", "o", "", "output value to file (ignores -m)")
|
cmd.PersistentFlags().StringVarP(&attr.File, "output", "o", "", "output value to file (ignores -m)")
|
||||||
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (simple|wide|json|template) (default 'simple')")
|
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "",
|
||||||
|
"output format (simple|wide|json|template) (default 'simple')")
|
||||||
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
|
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
|
||||||
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
|
cmd.PersistentFlags().BoolVarP(&conf.NoHumanize, "no-human", "N", false, "do not translate to human readable values")
|
||||||
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
|
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
|
||||||
@@ -207,6 +212,10 @@ func List(conf *cfg.Config) *cobra.Command {
|
|||||||
conf.Mode = "wide"
|
conf.Mode = "wide"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := conf.DB.Exists(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
entries, err := conf.DB.List(&attr, conf.Fulltext)
|
entries, err := conf.DB.List(&attr, conf.Fulltext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -216,7 +225,8 @@ func List(conf *cfg.Config) *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "", "output format (table|wide|json|template), wide is a verbose table. (default 'table')")
|
cmd.PersistentFlags().StringVarP(&conf.Mode, "mode", "m", "",
|
||||||
|
"output format (table|wide|json|template), wide is a verbose table. (default 'table')")
|
||||||
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
|
cmd.PersistentFlags().StringVarP(&conf.Template, "template", "T", "", "go template for '-m template'")
|
||||||
cmd.PersistentFlags().BoolVarP(&wide, "wide-output", "l", false, "output mode: wide")
|
cmd.PersistentFlags().BoolVarP(&wide, "wide-output", "l", false, "output mode: wide")
|
||||||
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
|
cmd.PersistentFlags().BoolVarP(&conf.NoHeaders, "no-headers", "n", false, "omit headers in tables")
|
||||||
|
|||||||
26
cmd/extra.go
26
cmd/extra.go
@@ -1,3 +1,5 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright © 2024 Thomas von Dein
|
Copyright © 2024 Thomas von Dein
|
||||||
|
|
||||||
@@ -14,7 +16,6 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -25,11 +26,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/app"
|
||||||
|
"codeberg.org/scip/anydb/cfg"
|
||||||
|
"codeberg.org/scip/anydb/output"
|
||||||
|
"codeberg.org/scip/anydb/rest"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tlinden/anydb/app"
|
|
||||||
"github.com/tlinden/anydb/cfg"
|
|
||||||
"github.com/tlinden/anydb/output"
|
|
||||||
"github.com/tlinden/anydb/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Export(conf *cfg.Config) *cobra.Command {
|
func Export(conf *cfg.Config) *cobra.Command {
|
||||||
@@ -57,6 +58,7 @@ func Export(conf *cfg.Config) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVarP(&attr.File, "output-file", "o", "", "filename or - for STDIN")
|
cmd.PersistentFlags().StringVarP(&attr.File, "output-file", "o", "", "filename or - for STDIN")
|
||||||
|
|
||||||
if err := cmd.MarkPersistentFlagRequired("output-file"); err != nil {
|
if err := cmd.MarkPersistentFlagRequired("output-file"); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -86,12 +88,14 @@ func Import(conf *cfg.Config) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print(out)
|
fmt.Print(out)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVarP(&attr.File, "import-file", "i", "", "filename or - for STDIN")
|
cmd.PersistentFlags().StringVarP(&attr.File, "import-file", "i", "", "filename or - for STDIN")
|
||||||
cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")
|
cmd.PersistentFlags().StringArrayVarP(&attr.Tags, "tags", "t", nil, "tags, multiple allowed")
|
||||||
|
|
||||||
if err := cmd.MarkPersistentFlagRequired("import-file"); err != nil {
|
if err := cmd.MarkPersistentFlagRequired("import-file"); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -124,7 +128,6 @@ func Man(conf *cfg.Config) *cobra.Command {
|
|||||||
man.Stderr = os.Stderr
|
man.Stderr = os.Stderr
|
||||||
|
|
||||||
err := man.Run()
|
err := man.Run()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute 'less': %w", err)
|
return fmt.Errorf("failed to execute 'less': %w", err)
|
||||||
}
|
}
|
||||||
@@ -205,19 +208,20 @@ func Edit(conf *cfg.Config) *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entry.Value) == 0 && entry.Binary {
|
if len(entry.GetValue()) == 0 && entry.GetBinary() {
|
||||||
return errors.New("key contains binary uneditable content")
|
return errors.New("key contains binary uneditable content")
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt if needed
|
// decrypt if needed
|
||||||
if entry.Encrypted {
|
if entry.GetEncrypted() {
|
||||||
pass, err := getPassword()
|
pass, err := getPassword()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
password = pass
|
password = pass
|
||||||
|
|
||||||
clear, err := app.Decrypt(pass, []byte(entry.Value))
|
clear, err := app.Decrypt(pass, []byte(entry.GetValue()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -232,7 +236,7 @@ func Edit(conf *cfg.Config) *cobra.Command {
|
|||||||
// save file to a temp file, call the editor with it, read
|
// save file to a temp file, call the editor with it, read
|
||||||
// it back in and compare the content with the original
|
// it back in and compare the content with the original
|
||||||
// one
|
// one
|
||||||
newcontent, err := editContent(editor, string(entry.Value))
|
newcontent, err := editContent(editor, entry.GetValue())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -328,7 +332,7 @@ func editContent(editor string, content string) (string, error) {
|
|||||||
|
|
||||||
newcontentstr := string(newcontent)
|
newcontentstr := string(newcontent)
|
||||||
if content == newcontentstr {
|
if content == newcontentstr {
|
||||||
return "", fmt.Errorf("content not modified, aborting")
|
return "", errors.New("content not modified, aborting")
|
||||||
}
|
}
|
||||||
|
|
||||||
return newcontentstr, nil
|
return newcontentstr, nil
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/app"
|
||||||
|
"codeberg.org/scip/anydb/cfg"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tlinden/anydb/app"
|
|
||||||
"github.com/tlinden/anydb/cfg"
|
|
||||||
"github.com/tlinden/yadu"
|
"github.com/tlinden/yadu"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,7 +69,6 @@ func Execute() {
|
|||||||
Short: "anydb",
|
Short: "anydb",
|
||||||
Long: `A personal key value store`,
|
Long: `A personal key value store`,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
var configs []string
|
var configs []string
|
||||||
if configfile != "" {
|
if configfile != "" {
|
||||||
configs = []string{configfile}
|
configs = []string{configfile}
|
||||||
@@ -110,12 +109,14 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf.DB = db
|
conf.DB = db
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if ShowVersion {
|
if ShowVersion {
|
||||||
fmt.Printf("This is anydb version %s\n", cfg.Version)
|
fmt.Printf("This is anydb version %s\n", cfg.Version)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ func CleanError(file string, err error) error {
|
|||||||
|
|
||||||
func FileExists(filename string) bool {
|
func FileExists(filename string) bool {
|
||||||
info, err := os.Stat(filename)
|
info, err := os.Stat(filename)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// return false on any error
|
// return false on any error
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ type Slogger struct {
|
|||||||
*slog.Logger
|
*slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Slogger) Debug(v ...interface{}) {}
|
func (l Slogger) Debug(v ...any) {}
|
||||||
func (l Slogger) Debugf(format string, v ...interface{}) { l.Logger.Debug(fmt.Sprintf(format, v...)) }
|
func (l Slogger) Debugf(format string, v ...any) { l.Logger.Debug(fmt.Sprintf(format, v...)) }
|
||||||
func (l Slogger) Error(v ...interface{}) {}
|
func (l Slogger) Error(v ...any) {}
|
||||||
func (l Slogger) Errorf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) }
|
func (l Slogger) Errorf(format string, v ...any) { l.Logger.Error(fmt.Sprintf(format, v...)) }
|
||||||
func (l Slogger) Info(v ...interface{}) {}
|
func (l Slogger) Info(v ...any) {}
|
||||||
func (l Slogger) Infof(format string, v ...interface{}) { l.Logger.Info(fmt.Sprintf(format, v...)) }
|
func (l Slogger) Infof(format string, v ...any) { l.Logger.Info(fmt.Sprintf(format, v...)) }
|
||||||
func (l Slogger) Warning(v ...interface{}) {}
|
func (l Slogger) Warning(v ...any) {}
|
||||||
func (l Slogger) Warningf(format string, v ...interface{}) { l.Logger.Warn(fmt.Sprintf(format, v...)) }
|
func (l Slogger) Warningf(format string, v ...any) { l.Warn(fmt.Sprintf(format, v...)) }
|
||||||
func (l Slogger) Fatal(v ...interface{}) {}
|
func (l Slogger) Fatal(v ...any) {}
|
||||||
func (l Slogger) Fatalf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) }
|
func (l Slogger) Fatalf(format string, v ...any) { l.Logger.Error(fmt.Sprintf(format, v...)) }
|
||||||
func (l Slogger) Panic(v ...interface{}) {}
|
func (l Slogger) Panic(v ...any) {}
|
||||||
func (l Slogger) Panicf(format string, v ...interface{}) { l.Logger.Error(fmt.Sprintf(format, v...)) }
|
func (l Slogger) Panicf(format string, v ...any) { l.Logger.Error(fmt.Sprintf(format, v...)) }
|
||||||
|
|||||||
@@ -177,6 +177,6 @@ Enter
|
|||||||
Sleep 3s
|
Sleep 3s
|
||||||
|
|
||||||
Enter
|
Enter
|
||||||
Type "# Try it out yourself: github.com/tlinden/anydb!"
|
Type "# Try it out yourself: codeberg.org/scip/anydb!"
|
||||||
Enter
|
Enter
|
||||||
Sleep 4s
|
Sleep 4s
|
||||||
|
|||||||
50
go.mod
50
go.mod
@@ -1,40 +1,38 @@
|
|||||||
module github.com/tlinden/anydb
|
module codeberg.org/scip/anydb
|
||||||
|
|
||||||
go 1.24.0
|
go 1.26.0
|
||||||
|
|
||||||
toolchain go1.24.1
|
toolchain go1.26.4
|
||||||
|
|
||||||
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/olekukonko/tablewriter v1.1.0
|
github.com/lmittmann/tint v1.2.0
|
||||||
github.com/pelletier/go-toml v1.9.5
|
github.com/olekukonko/tablewriter v1.1.4
|
||||||
github.com/rogpeppe/go-internal v1.14.1
|
github.com/pelletier/go-toml/v2 v2.4.3
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/rogpeppe/go-internal v1.15.0
|
||||||
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/tlinden/yadu v0.1.3
|
github.com/tlinden/yadu v0.1.3
|
||||||
go.etcd.io/bbolt v1.4.3
|
go.etcd.io/bbolt v1.5.0
|
||||||
golang.org/x/crypto v0.42.0
|
golang.org/x/crypto v0.42.0
|
||||||
golang.org/x/term v0.35.0
|
golang.org/x/term v0.35.0
|
||||||
google.golang.org/protobuf v1.36.9
|
google.golang.org/protobuf v1.36.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/fatih/color v1.16.0 // indirect
|
github.com/clipperhouse/displaywidth v0.10.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.6.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/olekukonko/errors v1.1.0 // indirect
|
github.com/mattn/go-runewidth v0.0.28 // indirect
|
||||||
github.com/olekukonko/ll v0.0.9 // indirect
|
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/olekukonko/errors v1.2.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
github.com/olekukonko/ll v0.1.8 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/valyala/fasthttp v1.55.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.36.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
|
||||||
)
|
)
|
||||||
|
|||||||
101
go.sum
101
go.sum
@@ -1,74 +1,71 @@
|
|||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g=
|
||||||
|
github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
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=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
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/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.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
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.2.0 h1:AogHRHy8HUJUnNJBHJlYa+fR4YY8mko2cnCp67xn9JY=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/lmittmann/tint v1.2.0/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.15/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.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo=
|
||||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
|
||||||
|
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
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=
|
go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
|
||||||
github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
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/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
24
main.go
24
main.go
@@ -23,8 +23,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/cmd"
|
||||||
"github.com/inconshreveable/mousetrap"
|
"github.com/inconshreveable/mousetrap"
|
||||||
"github.com/tlinden/anydb/cmd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -34,30 +34,13 @@ func main() {
|
|||||||
Main()
|
Main()
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// if we're running on Windows AND if the user double clicked the
|
|
||||||
// exe file from explorer, we tell them and then wait until any
|
|
||||||
// key has been hit, which will make the cmd window disappear and
|
|
||||||
// thus give the user time to read it.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if mousetrap.StartedByExplorer() {
|
|
||||||
fmt.Println("Do no double click anydb.exe!")
|
|
||||||
fmt.Println("Please open a command shell and run it from there.")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print("Press any key to quit: ")
|
|
||||||
_, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Main() int {
|
func Main() int {
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint gochecknoinits
|
||||||
func init() {
|
func init() {
|
||||||
// if we're running on Windows AND if the user double clicked the
|
// if we're running on Windows AND if the user double clicked the
|
||||||
// exe file from explorer, we tell them and then wait until any
|
// exe file from explorer, we tell them and then wait until any
|
||||||
@@ -69,6 +52,7 @@ func init() {
|
|||||||
fmt.Println("Please open a command shell and run it from there.")
|
fmt.Println("Please open a command shell and run it from there.")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Print("Press any key to quit: ")
|
fmt.Print("Press any key to quit: ")
|
||||||
|
|
||||||
_, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
_, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tlinden/anydb/app"
|
"codeberg.org/scip/anydb/app"
|
||||||
"github.com/tlinden/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WriteJSON(attr *app.DbAttr, conf *cfg.Config, entries app.DbEntries) error {
|
func WriteJSON(attr *app.DbAttr, conf *cfg.Config, entries app.DbEntries) error {
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
tpl "text/template"
|
tpl "text/template"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/app"
|
||||||
|
"codeberg.org/scip/anydb/cfg"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
"github.com/olekukonko/tablewriter/renderer"
|
"github.com/olekukonko/tablewriter/renderer"
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
"github.com/tlinden/anydb/app"
|
|
||||||
"github.com/tlinden/anydb/cfg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
func List(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
||||||
@@ -54,6 +54,7 @@ func ListJson(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(jsonentries))
|
fmt.Println(string(jsonentries))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ func ListTemplate(writer io.Writer, conf *cfg.Config, entries app.DbEntries) err
|
|||||||
|
|
||||||
for _, row := range entries {
|
for _, row := range entries {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
|
||||||
err = tmpl.Execute(&buf, row)
|
err = tmpl.Execute(&buf, row)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to execute output template: %w", err)
|
return fmt.Errorf("failed to execute output template: %w", err)
|
||||||
@@ -133,28 +135,27 @@ func ListTable(writer io.Writer, conf *cfg.Config, entries app.DbEntries) error
|
|||||||
case true:
|
case true:
|
||||||
if err :=
|
if err :=
|
||||||
table.Append([]string{
|
table.Append([]string{
|
||||||
row.Key,
|
row.GetKey(),
|
||||||
strings.Join(row.Tags, ","),
|
strings.Join(row.GetTags(), ","),
|
||||||
strconv.FormatUint(row.Size, 10),
|
strconv.FormatUint(row.GetSize(), 10),
|
||||||
row.Created.AsTime().Format("02.01.2006T03:04.05"),
|
row.GetCreated().AsTime().Format("02.01.2006T03:04.05"),
|
||||||
row.Preview,
|
row.GetPreview(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("failed to add data to table: %w", err)
|
return fmt.Errorf("failed to add data to table: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if err := table.Append([]string{
|
if err := table.Append([]string{
|
||||||
row.Key,
|
row.GetKey(),
|
||||||
strings.Join(row.Tags, ","),
|
strings.Join(row.GetTags(), ","),
|
||||||
humanize.Bytes(uint64(row.Size)),
|
humanize.Bytes(row.GetSize()),
|
||||||
humanize.Time(row.Created.AsTime()),
|
humanize.Time(row.GetCreated().AsTime()),
|
||||||
row.Preview,
|
row.GetPreview(),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("failed to add data to table: %w", err)
|
return fmt.Errorf("failed to add data to table: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if err := table.Append([]string{row.Key, row.Preview}); err != nil {
|
if err := table.Append([]string{row.GetKey(), row.GetPreview()}); err != nil {
|
||||||
return fmt.Errorf("failed to add data to table: %w", err)
|
return fmt.Errorf("failed to add data to table: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/app"
|
||||||
|
"codeberg.org/scip/anydb/cfg"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/tlinden/anydb/app"
|
|
||||||
"github.com/tlinden/anydb/cfg"
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
//"github.com/alecthomas/repr"
|
// "github.com/alecthomas/repr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
|
func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
|
||||||
@@ -40,17 +40,18 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn
|
|||||||
|
|
||||||
switch conf.Mode {
|
switch conf.Mode {
|
||||||
case "simple", "":
|
case "simple", "":
|
||||||
if entry.Binary {
|
if entry.GetBinary() {
|
||||||
if isatty {
|
if isatty {
|
||||||
fmt.Println("binary data omitted")
|
fmt.Println("binary data omitted")
|
||||||
} else {
|
} else {
|
||||||
if _, err := os.Stdout.WriteString(entry.Value); err != nil {
|
if _, err := os.Stdout.WriteString(entry.GetValue()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(string(entry.Value))
|
fmt.Print(entry.GetValue())
|
||||||
if entry.Value[entry.Size-1] != '\n' {
|
|
||||||
|
if entry.GetValue()[entry.GetSize()-1] != '\n' {
|
||||||
// always add a terminal newline
|
// always add a terminal newline
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
@@ -72,8 +73,10 @@ func Print(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
|
func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.DbEntry) error {
|
||||||
var fileHandle *os.File
|
var (
|
||||||
var err error
|
fileHandle *os.File
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if attr.File == "-" {
|
if attr.File == "-" {
|
||||||
fileHandle = os.Stdout
|
fileHandle = os.Stdout
|
||||||
@@ -92,10 +95,10 @@ func WriteFile(writer io.Writer, conf *cfg.Config, attr *app.DbAttr, entry *app.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// actually write file content
|
// actually write file content
|
||||||
_, err = fileHandle.WriteString(entry.Value)
|
_, err = fileHandle.WriteString(entry.GetValue())
|
||||||
|
|
||||||
if !entry.Binary {
|
if !entry.GetBinary() {
|
||||||
if entry.Value[entry.Size-1] != '\n' {
|
if entry.GetValue()[entry.GetSize()-1] != '\n' {
|
||||||
// always add a terminal newline
|
// always add a terminal newline
|
||||||
_, err = fileHandle.Write([]byte{'\n'})
|
_, err = fileHandle.Write([]byte{'\n'})
|
||||||
}
|
}
|
||||||
@@ -137,7 +140,7 @@ func Info(writer io.Writer, conf *cfg.Config, info *app.DbInfo) error {
|
|||||||
|
|
||||||
if conf.Debug {
|
if conf.Debug {
|
||||||
val := reflect.ValueOf(&bucket.Stats).Elem()
|
val := reflect.ValueOf(&bucket.Stats).Elem()
|
||||||
for i := 0; i < val.NumField(); i++ {
|
for i := range val.NumField() {
|
||||||
if _, err := fmt.Fprintf(writer, "%19s: %v\n", val.Type().Field(i).Name, val.Field(i)); err != nil {
|
if _, err := fmt.Fprintf(writer, "%19s: %v\n", val.Type().Field(i).Name, val.Field(i)); err != nil {
|
||||||
return fmt.Errorf("failed to write output: %w", err)
|
return fmt.Errorf("failed to write output: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
191
rest/handlers.go
191
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
|
||||||
@@ -17,15 +17,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"github.com/alecthomas/repr"
|
// "github.com/alecthomas/repr"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"encoding/json"
|
||||||
"github.com/tlinden/anydb/app"
|
"log"
|
||||||
"github.com/tlinden/anydb/cfg"
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/scip/anydb/app"
|
||||||
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SetContext struct {
|
type SetContext struct {
|
||||||
Query string `json:"query" form:"query"`
|
Query string `form:"query" json:"query"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListResponse struct {
|
type ListResponse struct {
|
||||||
@@ -40,101 +43,121 @@ 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.GetKey() == "" {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
101
rest/log.go
Normal file
101
rest/log.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package rest
|
||||||
|
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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"]
|
||||||
|
|
||||||
|
var contenttype string
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
119
rest/serve.go
119
rest/serve.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
|
||||||
@@ -17,11 +17,11 @@ 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"
|
|
||||||
"github.com/tlinden/anydb/cfg"
|
"codeberg.org/scip/anydb/cfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// used to return to the api client
|
// used to return to the api client
|
||||||
@@ -31,84 +31,61 @@ 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 {
|
|
||||||
// same thing as above but allows to supply parameters, see app.Dbattr{}
|
|
||||||
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("/", 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 {
|
|
||||||
// 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("GET "+apiprefix+"{$}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
Format: "${pid} ${ip}:${port} ${status} - ${method} ${path}\n",
|
RestList(w, r, conf)
|
||||||
DisableColors: true,
|
})
|
||||||
}))
|
|
||||||
|
|
||||||
router.Use(cors.New(cors.Config{
|
mux.HandleFunc("POST "+apiprefix+"{$}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
AllowMethods: "GET,PUT,POST,DELETE",
|
RestList(w, r, conf)
|
||||||
ExposeHeaders: "Content-Type,Accept",
|
})
|
||||||
}))
|
|
||||||
|
|
||||||
router.Use(compress.New(compress.Config{
|
mux.HandleFunc("GET "+apiprefix+"{key}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
Level: compress.LevelBestSpeed,
|
key := r.PathValue("key")
|
||||||
}))
|
RestGet(w, r, key, conf)
|
||||||
|
})
|
||||||
|
|
||||||
return router
|
mux.HandleFunc("DELETE "+apiprefix+"{key}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
key := r.PathValue("key")
|
||||||
|
RestDelete(w, r, key, conf)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("PUT "+apiprefix, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
RestSet(w, r, conf)
|
||||||
|
})
|
||||||
|
|
||||||
|
logger := LogHandler()
|
||||||
|
|
||||||
|
return http.ListenAndServe(conf.Listen, logger(mux))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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")
|
||||||
Code: code,
|
resp.WriteHeader(code)
|
||||||
Message: msg,
|
|
||||||
Success: success,
|
err := json.NewEncoder(resp).Encode(
|
||||||
})
|
Result{
|
||||||
|
Code: code,
|
||||||
|
Message: msg,
|
||||||
|
Success: success,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user