Store original documents for download after ingestion
Persist uploaded files to {data_dir}/documents/{content_hash}{ext} after
successful ingestion. Add GET /documents/{id}/file endpoint for retrieval,
delete stored files on document deletion, and add `kb export` client command.
Includes schema migration, tests, and spec updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kb-search/kb/internal/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export <id>",
|
||||
Short: "Download original document file",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runExport,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportCmd.Flags().StringP("output", "o", "", "output file path (default: original filename to current directory)")
|
||||
rootCmd.AddCommand(exportCmd)
|
||||
}
|
||||
|
||||
func runExport(cmd *cobra.Command, args []string) error {
|
||||
client := api.NewClient()
|
||||
resp, err := client.Get("/api/v1/documents/" + args[0] + "/file")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := api.CheckError(resp); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
outPath, _ := cmd.Flags().GetString("output")
|
||||
|
||||
if outPath == "" {
|
||||
// Try to get filename from Content-Disposition header
|
||||
cd := resp.Header.Get("Content-Disposition")
|
||||
if cd != "" {
|
||||
_, params, err := mime.ParseMediaType(cd)
|
||||
if err == nil && params["filename"] != "" {
|
||||
outPath = params["filename"]
|
||||
}
|
||||
}
|
||||
if outPath == "" {
|
||||
outPath = "document-" + args[0]
|
||||
}
|
||||
}
|
||||
|
||||
if outPath == "-" {
|
||||
_, err := io.Copy(os.Stdout, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
outPath = filepath.Clean(outPath)
|
||||
f, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create output file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
n, err := io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Saved %s (%d bytes)\n", outPath, n)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user