package services import ( "fmt" "io" "os" "path/filepath" ) // LocalBackend stores files on the local filesystem. type LocalBackend struct { baseDir string } // NewLocalBackend creates a local filesystem storage backend. // It ensures the base directory and standard subdirectories exist. func NewLocalBackend(baseDir string) (*LocalBackend, error) { if err := os.MkdirAll(baseDir, 0755); err != nil { return nil, fmt.Errorf("failed to create upload directory: %w", err) } for _, subdir := range []string{"images", "documents", "completions"} { path := filepath.Join(baseDir, subdir) if err := os.MkdirAll(path, 0755); err != nil { return nil, fmt.Errorf("failed to create subdirectory %s: %w", subdir, err) } } return &LocalBackend{baseDir: baseDir}, nil } func (b *LocalBackend) Write(key string, data []byte) error { destPath, err := SafeResolvePath(b.baseDir, key) if err != nil { return fmt.Errorf("invalid path: %w", err) } return os.WriteFile(destPath, data, 0644) } func (b *LocalBackend) Read(key string) ([]byte, error) { fullPath, err := SafeResolvePath(b.baseDir, key) if err != nil { return nil, fmt.Errorf("invalid path: %w", err) } return os.ReadFile(fullPath) } func (b *LocalBackend) Delete(key string) error { fullPath, err := SafeResolvePath(b.baseDir, key) if err != nil { return nil // invalid path = nothing to delete } if err := os.Remove(fullPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to delete file: %w", err) } return nil } func (b *LocalBackend) ReadStream(key string) (io.ReadCloser, error) { fullPath, err := SafeResolvePath(b.baseDir, key) if err != nil { return nil, fmt.Errorf("invalid path: %w", err) } return os.Open(fullPath) } // BaseDir returns the local storage base directory. func (b *LocalBackend) BaseDir() string { return b.baseDir }