diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-17 08:45:13 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-17 08:45:13 +0300 |
| commit | 9e2bf4af8b7b3b4ca2980aa6285482e7db0cd151 (patch) | |
| tree | 96bcd8ad2a64e6dfc9d3bedc9d063eeef1591cf0 /internal/keepass/remove.go | |
| parent | b53e348d89046ea8de5b82c283fb56980b53cdd8 (diff) | |
feat: add internal/keepass package with full Backend implementation (tasks k4+l4+m4)
Read-write KeePass backend implementing backend.Backend:
- WalkIndexes flattens groups into 'Group/Title' virtual entries
- LoadData returns formatted Password/User/URL/Notes for text entries
or raw bytes for binary attachments
- WriteBack hook enables edit round-trip through the kdbx file
- Add/Import/ImportRecursive/Remove with atomic tmp+rename save
- Binary attachments surface as virtual 'Group/Title/filename' entries;
Add to such a path creates/replaces the attachment on the parent entry
- AtomicSave exported for reuse by cli/kdbx_store.go
- parseContent is the tolerant inverse of formatContent
- Helpers EnsureGroup, UpsertEntryByTitle, SetEntryField, SplitDescriptionPath,
SanitizeRelativePath exported so cli/kdbx_store.go avoids duplication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/keepass/remove.go')
| -rw-r--r-- | internal/keepass/remove.go | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/internal/keepass/remove.go b/internal/keepass/remove.go new file mode 100644 index 0000000..40bbc9f --- /dev/null +++ b/internal/keepass/remove.go @@ -0,0 +1,106 @@ +package keepass + +import ( + "bufio" + "context" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "codeberg.org/snonux/foostore/internal/store" +) + +// Remove finds all indexes matching searchTerm and prompts before deleting. +func (b *Backend) Remove(ctx context.Context, searchTerm string, input io.Reader) error { + var indexes store.IndexSlice + if err := b.WalkIndexes(ctx, searchTerm, func(idx *store.Index) error { + indexes = append(indexes, idx) + return nil + }); err != nil { + return err + } + sort.Sort(indexes) + + scanner := bufio.NewScanner(input) + for _, idx := range indexes { + if err := b.confirmAndRemove(idx, scanner); err != nil { + return err + } + } + return nil +} + +// confirmAndRemove prompts the user for deletion confirmation and removes the +// entry (or attachment) on "y". +func (b *Backend) confirmAndRemove(idx *store.Index, scanner *bufio.Scanner) error { + for { + fmt.Print(idx.String()) + fmt.Print("You really want to delete this? (y/n): ") + if !scanner.Scan() { + return nil + } + switch strings.TrimSpace(scanner.Text()) { + case "y": + return b.removeEntry(idx.Description) + case "n": + return nil + } + } +} + +// removeEntry deletes the KeePass entry (or attachment) matching description +// and saves the DB. +// +// If description is a virtual attachment path ("Group/Title/file.bin"), the +// attachment is removed from the parent entry rather than removing the entry +// itself. For regular entries the full entry is deleted from its group. +func (b *Backend) removeEntry(description string) error { + // Detect virtual attachment paths: if the parent exists as a text entry but + // description itself does not, route to attachment removal. + if parentDesc, attachName, ok := b.isAttachmentPath(description); ok { + return b.removeAttachment(parentDesc, attachName) + } + return b.removeTextEntry(description) +} + +// removeTextEntry deletes a regular (non-attachment) entry from its group. +func (b *Backend) removeTextEntry(description string) error { + groupPath, title, err := SplitDescriptionPath(description) + if err != nil { + return fmt.Errorf("keepass remove: %w", err) + } + g := EnsureGroup(b.root(), groupPath) + for i, e := range g.Entries { + if e.GetTitle() == title { + g.Entries = append(g.Entries[:i], g.Entries[i+1:]...) + return b.save() + } + } + return fmt.Errorf("keepass remove: entry %q not found", description) +} + +// ShredAllExported securely deletes every regular file in cfg.ExportDir. +// Delegates to store.ShredFile for the actual destruction. +func (b *Backend) ShredAllExported(ctx context.Context) error { + entries, err := os.ReadDir(b.cfg.ExportDir) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("listing export dir: %w", err) + } + var lastErr error + for _, e := range entries { + if e.IsDir() { + continue + } + filePath := filepath.Join(b.cfg.ExportDir, e.Name()) + if err := store.ShredFile(ctx, filePath); err != nil { + lastErr = err + } + } + return lastErr +} |
