Documentation
¶
Overview ¶
Package storage keeps files: uploads, generated exports, anything that doesn't belong in the database. A Bucket is a directory on disk or an S3-compatible bucket (AWS S3, Cloudflare R2, MinIO, Backblaze B2, ...):
files, err := storage.Disk("data/files")
// or: storage.NewS3(storage.S3Options{Endpoint: "https://s3.eu-central-1.amazonaws.com", Region: "eu-central-1", Bucket: "acme", AccessKey: id, SecretKey: secret})
// In an action:
uploads, err := storage.Receive(ctx, "avatar", storage.ReceiveOptions{MaxSize: 5 << 20, Types: []string{"image/*"}})
key := storage.NewKey("avatars", uploads[0].Filename)
err = uploads[0].Save(ctx, files, key)
// Serving them, with links that expire:
signer := storage.NewSigner(secret)
kit.WithPlugins(storage.Serve("/files/", files, storage.ServeOptions{Signer: signer}))
url := signer.URL("/files/", key, time.Hour)
Index ¶
- Variables
- func NewKey(prefix, filename string) string
- func Serve(prefix string, b Bucket, opts ServeOptions) kit.Plugin
- func ValidKey(key string) error
- type Bucket
- type DiskBucket
- func (b *DiskBucket) Delete(_ context.Context, key string) error
- func (b *DiskBucket) Get(_ context.Context, key string) (io.ReadCloser, Object, error)
- func (b *DiskBucket) List(_ context.Context, prefix string) ([]Object, error)
- func (b *DiskBucket) Put(ctx context.Context, key string, r io.Reader, _ int64, contentType string) error
- func (b *DiskBucket) Stat(_ context.Context, key string) (Object, error)
- type InvalidUpload
- type Object
- type Presigner
- type ReceiveOptions
- type S3Bucket
- func (b *S3Bucket) Delete(ctx context.Context, key string) error
- func (b *S3Bucket) Get(ctx context.Context, key string) (io.ReadCloser, Object, error)
- func (b *S3Bucket) List(ctx context.Context, prefix string) ([]Object, error)
- func (b *S3Bucket) PresignGet(key string, ttl time.Duration) (string, error)
- func (b *S3Bucket) PresignPut(key string, ttl time.Duration) (string, error)
- func (b *S3Bucket) Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
- func (b *S3Bucket) Stat(ctx context.Context, key string) (Object, error)
- type S3Options
- type ServeOptions
- type Signer
- type Upload
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("storage: not found")
ErrNotFound is returned for keys that don't exist.
Functions ¶
func NewKey ¶
NewKey returns a new unique key under prefix for a file named filename, such as "avatars/2026/11/3f9a1c2e7b4d-portrait.png". The random part keeps keys unguessable; the name and extension are kept readable.
func Serve ¶
func Serve(prefix string, b Bucket, opts ServeOptions) kit.Plugin
Serve returns a plugin that serves bucket's files under prefix, such as "/files/". Files are sent with nosniff and a sandboxing Content Security Policy; disk files support range requests, and buckets that can presign (S3) are redirected to directly.
Types ¶
type Bucket ¶
type Bucket interface {
// Put stores the contents of r under key, replacing any file there.
// size is the length of r, or -1 if unknown.
Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
// Get opens a file. The reader is an io.ReadSeeker when the bucket can
// seek, which lets Serve answer range requests.
Get(ctx context.Context, key string) (io.ReadCloser, Object, error)
Stat(ctx context.Context, key string) (Object, error)
// Delete removes a file; deleting a missing file is not an error.
Delete(ctx context.Context, key string) error
// List returns the files whose keys start with prefix, sorted by key.
List(ctx context.Context, prefix string) ([]Object, error)
}
Bucket stores files by key. Keys are slash-separated paths such as "avatars/2026/11/a1b2c3.png"; see ValidKey.
type DiskBucket ¶
type DiskBucket struct {
// contains filtered or unexported fields
}
DiskBucket stores files in a directory. Content types are kept next to the files, in a .tern-meta directory.
func Disk ¶
func Disk(dir string) (*DiskBucket, error)
Disk returns a bucket in dir, creating it if needed.
func (*DiskBucket) Delete ¶
func (b *DiskBucket) Delete(_ context.Context, key string) error
Delete implements Bucket.
func (*DiskBucket) Get ¶
func (b *DiskBucket) Get(_ context.Context, key string) (io.ReadCloser, Object, error)
Get implements Bucket; the reader is an *os.File.
type InvalidUpload ¶
InvalidUpload is returned for files Receive refuses; show Message next to the field, with kit.Fail.
func (*InvalidUpload) Error ¶
func (e *InvalidUpload) Error() string
type Object ¶
type Object struct {
Key string `json:"key"`
Size int64 `json:"size"`
ContentType string `json:"contentType"`
Modified time.Time `json:"modified"`
}
Object describes a stored file.
type Presigner ¶
Presigner is implemented by buckets whose files can be fetched directly with a time-limited URL, such as S3; Serve redirects to it.
type ReceiveOptions ¶
type ReceiveOptions struct {
// MaxSize is the largest file accepted, in bytes; the default is 10 MB.
MaxSize int64
// MaxFiles is how many files the field may have; the default is 1.
MaxFiles int
// Types lists accepted content types, such as "image/*" or
// "application/pdf"; by default any type is accepted.
Types []string
// Required makes an empty field an error.
Required bool
}
ReceiveOptions limit what Receive accepts.
type S3Bucket ¶
type S3Bucket struct {
// contains filtered or unexported fields
}
S3Bucket is a bucket on an S3-compatible service. Requests are signed with AWS Signature Version 4.
func (*S3Bucket) PresignGet ¶
PresignGet implements Presigner: a URL that downloads key until ttl passes (at most 7 days).
func (*S3Bucket) PresignPut ¶
PresignPut returns a URL that browsers can PUT a file to directly, so large uploads don't pass through the app.
type S3Options ¶
type S3Options struct {
// Endpoint is the service URL, such as
// "https://s3.eu-central-1.amazonaws.com", "https://<account>.r2.cloudflarestorage.com"
// or "http://localhost:9000" for MinIO.
Endpoint string
// Region is the bucket's region; "auto" for R2, "us-east-1" for MinIO.
Region string
Bucket string
AccessKey string
SecretKey string
// PathStyle addresses the bucket as <endpoint>/<bucket>/<key> instead
// of <bucket>.<endpoint host>/<key>. MinIO and most self-hosted servers
// need it.
PathStyle bool
// HTTPClient is used for requests; the default has a 5 minute timeout.
HTTPClient *http.Client
}
S3Options configure an S3-compatible bucket.
type ServeOptions ¶
type ServeOptions struct {
// Signer, if set, makes every file private: only links from
// Signer.URL work. Without it, files are public.
Signer *Signer
// CacheControl is sent with files; the default is
// "private, max-age=3600" for signed files and "public, max-age=3600"
// otherwise.
CacheControl string
// Inline lists content types shown in the browser rather than
// downloaded. The default is images (except SVG), video, audio, PDF and
// plain text; everything else is sent as an attachment, so uploaded
// HTML or SVG can't run scripts on your site.
Inline []string
}
ServeOptions configure Serve.
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer makes links to private files that expire.
type Upload ¶
type Upload struct {
// Filename is the name the browser sent, without directories. Use it
// for display only; NewKey makes a safe key from it.
Filename string
Size int64
// ContentType is detected from the file's contents, not taken from
// the browser.
ContentType string
// contains filtered or unexported fields
}
Upload is a file sent with a form.
func Receive ¶
Receive reads the files of a form field in an action or endpoint. It raises the request's body limit to fit MaxFiles files of MaxSize, checks each file's size and its type, detected from its first bytes, and returns an *InvalidUpload error for files it refuses:
uploads, err := storage.Receive(ctx, "avatar", storage.ReceiveOptions{MaxSize: 2 << 20, Types: []string{"image/png", "image/jpeg"}})
var invalid *storage.InvalidUpload
if errors.As(err, &invalid) {
return nil, kit.Fail(400, Invalid{Error: invalid.Message})
}
The form must use enctype="multipart/form-data".