update linter config, satisfy linter (#28)

This commit is contained in:
T. von Dein
2026-08-31 22:06:25 +02:00
parent 5c9900d985
commit 1d45b2c6fe
24 changed files with 331 additions and 221 deletions

View File

@@ -1,6 +0,0 @@
linters:
exclusions:
rules:
- linters:
- staticcheck
text: "QF1008:"

View File

@@ -1,7 +0,0 @@
version: "2"
linters:
exclusions:
rules:
- linters:
- staticcheck
text: "QF1008:"

97
.golangci.yml Normal file
View 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

View File

@@ -3,6 +3,9 @@ matrix:
- linux/amd64 - linux/amd64
goversion: goversion:
- 1.24 - 1.24
lintversion:
- v2.12.2
labels: labels:
platform: ${platform} platform: ${platform}
@@ -21,7 +24,7 @@ steps:
event: [push] event: [push]
image: golang:${goversion} image: golang:${goversion}
commands: commands:
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${lintversion}
- golangci-lint --version - golangci-lint --version
- golangci-lint run ./... - golangci-lint run ./...
depends_on: [build] depends_on: [build]

View File

@@ -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

12
anydb.1
View File

@@ -133,13 +133,12 @@
.\" ======================================================================== .\" ========================================================================
.\" .\"
.IX Title "ANYDB 1" .IX Title "ANYDB 1"
.TH ANYDB 1 "2026-04-29" "1" "User Commands" .TH ANYDB 1 "2026-08-31" "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
@@ -617,6 +616,9 @@ The following template variables can be used:
To be able to use the value in a template, you'll need to add the 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 \&\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. 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 .IP "\fB.Preview\fR string" 4
.IX Item ".Preview string" .IX Item ".Preview string"
First line of entry value (if it is a string). First line of entry value (if it is a string).

View File

@@ -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:

View File

@@ -1,3 +1,5 @@
package app
/* /*
Copyright © 2024-2025 Thomas von Dein Copyright © 2024-2025 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 (
"fmt" "fmt"
@@ -125,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)
} }

View File

@@ -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,7 +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: ")
pass, err := term.ReadPassword(int(syscall.Stdin))
pass, err := term.ReadPassword(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 +59,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 +82,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 +99,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 +108,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 +148,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])

182
app/db.go
View File

@@ -25,6 +25,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"strings" "strings"
"time" "time"
@@ -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"
@@ -121,6 +122,7 @@ func (db *DB) Open() error {
} }
db.DB = b db.DB = b
return nil return nil
} }
@@ -136,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
@@ -166,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 {
@@ -177,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)
@@ -187,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
} }
} }
@@ -247,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") {
@@ -255,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()
} }
} }
@@ -269,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))
@@ -285,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)
} }
@@ -297,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
} }
@@ -312,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
@@ -380,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)
} }
@@ -426,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))
} }
@@ -439,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
@@ -470,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)
} }
@@ -482,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)
} }
@@ -490,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)
} }
@@ -528,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
@@ -567,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)
@@ -580,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
@@ -595,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
}

View File

@@ -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

View File

@@ -83,6 +83,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)

View File

@@ -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,7 +407,7 @@ 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:
@@ -417,6 +417,9 @@ TEMPLATES
"-s" flag so that values are included in output data stream. "-s" flag so that values are included in output data stream.
Otherwise you can only use ".Preview". 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 .Preview string
First line of entry value (if it is a string). First line of entry value (if it is a string).

View File

@@ -116,19 +116,19 @@ func Get(conf *cfg.Config) *cobra.Command {
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
} }
@@ -137,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'")
@@ -224,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")

View File

@@ -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"
"github.com/spf13/cobra"
"codeberg.org/scip/anydb/app" "codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg" "codeberg.org/scip/anydb/cfg"
"codeberg.org/scip/anydb/output" "codeberg.org/scip/anydb/output"
"codeberg.org/scip/anydb/rest" "codeberg.org/scip/anydb/rest"
"github.com/spf13/cobra"
) )
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

View File

@@ -24,9 +24,9 @@ import (
"path/filepath" "path/filepath"
"runtime/debug" "runtime/debug"
"github.com/spf13/cobra"
"codeberg.org/scip/anydb/app" "codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg" "codeberg.org/scip/anydb/cfg"
"github.com/spf13/cobra"
"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
} }

View File

@@ -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

View File

@@ -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...)) }

24
main.go
View File

@@ -23,8 +23,8 @@ import (
"os" "os"
"runtime" "runtime"
"github.com/inconshreveable/mousetrap"
"codeberg.org/scip/anydb/cmd" "codeberg.org/scip/anydb/cmd"
"github.com/inconshreveable/mousetrap"
) )
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)

View File

@@ -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"
"codeberg.org/scip/anydb/app"
"codeberg.org/scip/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)
} }
} }

View File

@@ -24,11 +24,11 @@ import (
"os" "os"
"reflect" "reflect"
"github.com/dustin/go-humanize"
"codeberg.org/scip/anydb/app" "codeberg.org/scip/anydb/app"
"codeberg.org/scip/anydb/cfg" "codeberg.org/scip/anydb/cfg"
"github.com/dustin/go-humanize"
"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)
} }

View File

@@ -17,7 +17,7 @@ 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"
"encoding/json" "encoding/json"
"log" "log"
@@ -28,7 +28,7 @@ import (
) )
type SetContext struct { type SetContext struct {
Query string `json:"query" form:"query"` Query string `form:"query" json:"query"`
} }
type ListResponse struct { type ListResponse struct {
@@ -50,6 +50,7 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
if err != nil { if err != nil {
if err.Error() != `EOF` { if err.Error() != `EOF` {
http.Error(resp, err.Error(), http.StatusBadRequest) http.Error(resp, err.Error(), http.StatusBadRequest)
return return
} }
} }
@@ -58,6 +59,7 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
entries, err := conf.DB.List(attr, attr.Fulltext) entries, err := conf.DB.List(attr, attr.Fulltext)
if err != nil { if err != nil {
JsonStatus(resp, http.StatusForbidden, "Unable to list keys: "+err.Error()) JsonStatus(resp, http.StatusForbidden, "Unable to list keys: "+err.Error())
return return
} }
@@ -69,7 +71,6 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
Success: true, Success: true,
Entries: entries, Entries: entries,
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -78,6 +79,7 @@ func RestList(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) { func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) {
if key == "" { if key == "" {
JsonStatus(resp, http.StatusForbidden, "key not provided") JsonStatus(resp, http.StatusForbidden, "key not provided")
return return
} }
@@ -85,10 +87,13 @@ func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.
entry, err := conf.DB.Get(&app.DbAttr{Key: key}) entry, err := conf.DB.Get(&app.DbAttr{Key: key})
if err != nil { if err != nil {
JsonStatus(resp, http.StatusForbidden, "Unable to get key: "+err.Error()) JsonStatus(resp, http.StatusForbidden, "Unable to get key: "+err.Error())
return return
} }
if entry.Key == "" {
if entry.GetKey() == "" {
JsonStatus(resp, http.StatusForbidden, "Key does not exist") JsonStatus(resp, http.StatusForbidden, "Key does not exist")
return return
} }
@@ -100,7 +105,6 @@ func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.
Success: true, Success: true,
Entry: entry, Entry: entry,
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -109,6 +113,7 @@ func RestGet(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.
func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) { func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *cfg.Config) {
if key == "" { if key == "" {
JsonStatus(resp, http.StatusForbidden, "key not provided") JsonStatus(resp, http.StatusForbidden, "key not provided")
return return
} }
@@ -116,6 +121,7 @@ func RestDelete(resp http.ResponseWriter, req *http.Request, key string, conf *c
err := conf.DB.Del(&app.DbAttr{Key: key}) err := conf.DB.Del(&app.DbAttr{Key: key})
if err != nil { if err != nil {
JsonStatus(resp, http.StatusForbidden, "Unable to delete key: "+err.Error()) JsonStatus(resp, http.StatusForbidden, "Unable to delete key: "+err.Error())
return return
} }
@@ -128,6 +134,7 @@ func RestSet(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
err := json.NewDecoder(req.Body).Decode(&attr) err := json.NewDecoder(req.Body).Decode(&attr)
if err != nil { if err != nil {
http.Error(resp, err.Error(), http.StatusBadRequest) http.Error(resp, err.Error(), http.StatusBadRequest)
return return
} }
@@ -141,6 +148,7 @@ func RestSet(resp http.ResponseWriter, req *http.Request, conf *cfg.Config) {
err = conf.DB.Set(attr) err = conf.DB.Set(attr)
if err != nil { if err != nil {
JsonStatus(resp, http.StatusForbidden, "Unable to set key: "+err.Error()) JsonStatus(resp, http.StatusForbidden, "Unable to set key: "+err.Error())
return return
} }

View File

@@ -1,3 +1,5 @@
package rest
/* /*
This logging middleware is based on This logging middleware is based on
@@ -7,7 +9,6 @@ https://github.com/elithrar/admission-control/blob/v0.6.3/request_logger.go
I am using slog and added a couple of small modifications. I am using slog and added a couple of small modifications.
*/ */
package rest
import ( import (
"log/slog" "log/slog"
@@ -20,6 +21,7 @@ import (
// written HTTP status code to be captured for logging. // written HTTP status code to be captured for logging.
type responseWriter struct { type responseWriter struct {
http.ResponseWriter http.ResponseWriter
status int status int
size int size int
wroteHeader bool wroteHeader bool
@@ -48,7 +50,6 @@ func (rw *responseWriter) WriteHeader(code int) {
} }
func (rw *responseWriter) Write(data []byte) (int, error) { func (rw *responseWriter) Write(data []byte) (int, error) {
written, err := rw.ResponseWriter.Write(data) written, err := rw.ResponseWriter.Write(data)
rw.size += written rw.size += written
@@ -75,7 +76,9 @@ func LogHandler() func(http.Handler) http.Handler {
next.ServeHTTP(wrapped, req) next.ServeHTTP(wrapped, req)
header := wrapped.Header()["Content-Type"] header := wrapped.Header()["Content-Type"]
contenttype := ""
var contenttype string
if header == nil { if header == nil {
contenttype = "text/plain" contenttype = "text/plain"
} else { } else {

View File

@@ -85,7 +85,6 @@ func JsonStatus(resp http.ResponseWriter, code int, msg string) {
Message: msg, Message: msg,
Success: success, Success: success,
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }