Files
scannerbot/server/api/file.go
T

157 lines
3.2 KiB
Go

package api
import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path/filepath"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/google/uuid"
)
func initFileStore() FileStore {
val, ok := os.LookupEnv("FILE_BACKEND")
if !ok {
slog.Error("FILE_BACKEND environment variable not set")
os.Exit(1)
}
switch FileBackend(val) {
case FileBackendLocal:
localFilePath, ok := os.LookupEnv("LOCAL_FILEPATH")
if !ok {
slog.Error("LOCAL_FILEPATH environment variable not set")
os.Exit(1)
}
return &LocalFileStore{BaseDir: localFilePath}
}
slog.Error("unsupported FILE_BACKEND", "value", val)
os.Exit(1)
return nil
}
type File struct {
ID uuid.UUID
Name string
Created time.Time
Backend FileBackend
Path string
}
type FileBackend string
const (
FileBackendLocal FileBackend = "local"
FileBackendS3 FileBackend = "s3"
)
var Store FileStore
type FileStore interface {
Save(name string, r io.Reader) (*File, error)
URL(file *File) (string, error)
}
type LocalFileStore struct {
BaseDir string
}
func (s *LocalFileStore) Save(name string, r io.Reader) (*File, error) {
id := uuid.New()
path := filepath.Join(s.BaseDir, id.String())
f, err := os.Create(path)
if err != nil {
return nil, fmt.Errorf("file(local): failed to create file: %w", err)
}
defer f.Close()
if _, err := io.Copy(f, r); err != nil {
os.Remove(path)
return nil, fmt.Errorf("file(local): failed to write file: %w", err)
}
return &File{
ID: id,
Name: name,
Created: time.Now(),
Backend: FileBackendLocal,
Path: path,
}, nil
}
func (s *LocalFileStore) URL(file *File) (string, error) {
return "/files/" + file.ID.String(), nil
}
func ServeFile(w http.ResponseWriter, r *http.Request) {
slog.Debug("file: entering ServeFile handler")
fileID := chi.URLParam(r, "fileID")
parsed, err := uuid.Parse(fileID)
if err != nil {
render.Render(w, r, ErrInvalidRequest(err))
return
}
file, err := dbGetFile(parsed.String())
if err != nil {
if errors.Is(err, ErrFileNotFound) {
render.Render(w, r, ErrNotFound)
} else {
slog.Error("file: failed to fetch file", "fileid", parsed.String(), "error", err)
render.Render(w, r, ErrInternal(err))
}
return
}
f, err := os.Open(file.Path)
if err != nil {
slog.Error("file: failed to open file", "fileid", file.ID, "error", err)
render.Render(w, r, ErrInternal(err))
return
}
defer f.Close()
http.ServeContent(w, r, file.Name, file.Created, f)
}
// UploadFile is a temporary handler for testing file uploads.
/*
func UploadFile(w http.ResponseWriter, r *http.Request) {
slog.Debug("file: entering UploadFile handler")
if err := r.ParseMultipartForm(32 << 20); err != nil {
render.Render(w, r, ErrInvalidRequest(err))
return
}
f, header, err := r.FormFile("file")
if err != nil {
render.Render(w, r, ErrInvalidRequest(err))
return
}
defer f.Close()
file, err := Store.Save(header.Filename, f)
if err != nil {
slog.Error("file: failed to save file", "error", err)
render.Render(w, r, ErrInternal(err))
return
}
if err := dbAddFile(file); err != nil {
render.Render(w, r, ErrInternal(err))
return
}
slog.Debug("file: uploaded file", "fileid", file.ID, "filename", file.Name)
render.Render(w, r, NewFilePayloadResponse(file))
}
*/