package media import ( "context" "fmt" "io" "os" "path/filepath" "strings" ) // LocalStorage saves uploads under a directory on disk and serves them via // a static file route (mounted by main.go at /uploads when this driver is // active). type LocalStorage struct { dir string baseURL string } func NewLocalStorage(dir, baseURL string) (*LocalStorage, error) { if err := os.MkdirAll(dir, 0o755); err != nil { return nil, fmt.Errorf("create upload dir: %w", err) } return &LocalStorage{dir: dir, baseURL: strings.TrimRight(baseURL, "/")}, nil } func (s *LocalStorage) Save(_ context.Context, key string, reader io.Reader, _ int64, _ string) (string, error) { // key is always a freshly generated UUID-based name (see service.go), // never derived from user input, so path traversal is not reachable here. dest := filepath.Join(s.dir, filepath.Base(key)) f, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644) if err != nil { return "", fmt.Errorf("create file: %w", err) } defer f.Close() if _, err := io.Copy(f, reader); err != nil { return "", fmt.Errorf("write file: %w", err) } return s.baseURL + "/" + filepath.Base(key), nil } func (s *LocalStorage) Delete(_ context.Context, key string) error { err := os.Remove(filepath.Join(s.dir, filepath.Base(key))) if err != nil && !os.IsNotExist(err) { return fmt.Errorf("delete file: %w", err) } return nil }