Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .go-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.24.0
138 changes: 138 additions & 0 deletions filestorage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
## Configuration

The file storage system is configured using a YAML or JSON file. Here's an example configuration:

```yaml
storage:
default: "s3"
disks:
- driver: "local"
name: "local"
local:
rootPath: "./storage"
baseUrl: "http://localhost:8080/storage"

- driver: "local"
name: "local-2"
local:
rootPath: "./storage2"
baseUrl: "http://localhost:8080/storage2"

- driver: "s3"
name: "s3"
s3:
region: "ap-southeast-1"
bucket: "bucket-1"
profile: "rayyone"

- driver: "s3"
name: "s3-2"
s3:
region: "ap-southeast-1"
bucket: "bucket-2"
accessKey: "<YOUR_ACCESS_KEY>"
secretKey: "<YOUR_SECRET_KEY>"
```

---

# File Storage Usage Example

This document shows how to use the `fileStorage` abstraction with different disk drivers (e.g., local, S3).

## fileStorage Initialize
```go
filestorage.Initialize(config.Storage.Disks, config.Storage.Default)
```
---

## Put using Default Disk

```go
filename := "uploads/default-file.txt"
file := strings.NewReader("Hello from default disk!")

err := fileStorage.Put(filename, file)
if err != nil {
log.Fatalf("Put failed: %v", err)
}
fmt.Println("Uploaded using default disk.")
```

> ✅ Uses the default disk defined in your config: `storage.default`

---


## 📤 Put using Specific Disk (e.g., S3)

```go
filename := "uploads/s3-specific.txt"
file := strings.NewReader("Hello from S3!")

err := fileStorage.Disk("s3-2").Put(filename, file)
if err != nil {
log.Fatalf("S3 Put failed: %v", err)
}
fmt.Println("Uploaded using S3 disk.")
```

---

## Get File Content

```go
data, err := fileStorage.Get("uploads/default-file.txt")
fmt.Println(string(data))
```

---

## Delete File

```go
err := fileStorage.Delete("uploads/default-file.txt")
```

---

## Get URL

```go
url := fileStorage.URL("uploads/default-file.txt")
fmt.Println("URL:", url)
```

---

## Get SignedURL

```go
url := fileStorage.SignedURL("uploads/default-file.txt")
fmt.Println("URL:", url)
```

---

## Copy

```go
fileStorage.Copy("from/file.txt", "to/file.txt")
```

---

## Move

```go
fileStorage.Move("from/file.txt", "to/file.txt")
```

---

## ⚠️ Notes on `Disk(...)`

- `fileStorage.Disk("s3-2")` allows choosing a specific disk explicitly, regardless of the default.
- Useful when you have multiple S3 buckets or local disks:


27 changes: 27 additions & 0 deletions filestorage/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package config

import "github.com/rayyone/go-core/filestorage/contracts"

type S3Config struct {
Region string
Bucket string
BaseURL string
CloudFrontURL string
Profile string
AccessKey string
SecretKey string
SessionToken string
Endpoint string
}

type LocalConfig struct {
RootPath string
BaseURL string
}

type FileStorageConfig struct {
Name string
Driver contracts.DriverType
S3 S3Config
Local LocalConfig
}
39 changes: 39 additions & 0 deletions filestorage/contracts/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package contracts

import (
"io"
"time"
)

type Option func(*PutOptions)

type Storage interface {
Put(path string, content io.Reader, options ...Option) (*string, error)
PutFile(path string, filePath string, options ...Option) (*string, error)
Get(path string) ([]byte, error)
GetStream(path string) (io.ReadCloser, error)
URL(path string) string
SignedURL(path string, expiration time.Duration) (string, error)
Download(path string, localPath string) error
Delete(path string) error
Exists(path string) bool
Copy(from, to string) error
Move(from, to string) error
Size(path string) (int64, error)
}
type ACLType string
type DiskType string
type DriverType string
type PutOptions struct {
ACL ACLType
MimeType string
ContentDisposition string
Bucket string
CacheControl string
ContentEncoding string
ContentLanguage string
Expires *time.Time
Metadata map[string]string
ServerSideEncryption string
StorageClass string
}
114 changes: 114 additions & 0 deletions filestorage/drivers/local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package drivers

import (
"bytes"
"fmt"
"github.com/rayyone/go-core/filestorage/config"
"github.com/rayyone/go-core/filestorage/contracts"
"io"
"os"
"path/filepath"
"strings"
"time"
)

type LocalDisk struct {
root string
url string
}

// NewLocalDisk creates a new local disk storage
func NewLocalDisk(config config.LocalConfig) *LocalDisk {
return &LocalDisk{
root: config.RootPath,
url: strings.TrimSuffix(config.BaseURL, "/"),
}
}
func (ld *LocalDisk) Put(path string, content io.Reader, options ...contracts.Option) (*string, error) {
fullPath := filepath.Join(ld.root, path)

if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
return nil, err
}

file, err := os.Create(fullPath)
if err != nil {
return nil, err
}
defer file.Close()

_, err = io.Copy(file, content)
return &fullPath, err
}

func (ld *LocalDisk) PutFile(path string, filePath string, options ...contracts.Option) (*string, error) {
sourceFile, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer sourceFile.Close()

return ld.Put(path, sourceFile, options...)
}

func (ld *LocalDisk) Get(path string) ([]byte, error) {
fullPath := filepath.Join(ld.root, path)
return os.ReadFile(fullPath)
}

func (ld *LocalDisk) GetStream(path string) (io.ReadCloser, error) {
fullPath := filepath.Join(ld.root, path)
return os.Open(fullPath)
}

func (ld *LocalDisk) URL(path string) string {
return fmt.Sprintf("%s/%s", ld.url, strings.TrimPrefix(path, "/"))
}

func (ld *LocalDisk) SignedURL(path string, expiration time.Duration) (string, error) {
return ld.URL(path), nil
}

func (ld *LocalDisk) Download(path string, localPath string) error {
content, err := ld.Get(path)
if err != nil {
return err
}
return os.WriteFile(localPath, content, 0644)
}

func (ld *LocalDisk) Delete(path string) error {
fullPath := filepath.Join(ld.root, path)
return os.Remove(fullPath)
}

func (ld *LocalDisk) Exists(path string) bool {
fullPath := filepath.Join(ld.root, path)
_, err := os.Stat(fullPath)
return err == nil
}

func (ld *LocalDisk) Copy(from, to string) error {
content, err := ld.Get(from)
if err != nil {
return err
}
_, err = ld.Put(to, bytes.NewReader(content))
return err
}

func (ld *LocalDisk) Move(from, to string) error {
if err := ld.Copy(from, to); err != nil {
return err
}
return ld.Delete(from)
}

func (ld *LocalDisk) Size(path string) (int64, error) {
fullPath := filepath.Join(ld.root, path)
stat, err := os.Stat(fullPath)
if err != nil {
return 0, err
}
return stat.Size(), nil
}
Loading