157 lines
3.2 KiB
Go
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))
|
|
}
|
|
*/
|