storage

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: MIT Imports: 25 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("storage: not found")

ErrNotFound is returned for keys that don't exist.

Functions

func NewKey

func NewKey(prefix, filename string) string

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.

func ValidKey

func ValidKey(key string) error

ValidKey reports whether key is safe to use with every bucket: segments of letters, digits and !_.*'()@ +,=- separated by single slashes, no "." or ".." segments, and at most 1024 bytes.

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

Get implements Bucket; the reader is an *os.File.

func (*DiskBucket) List

func (b *DiskBucket) List(_ context.Context, prefix string) ([]Object, error)

List implements Bucket.

func (*DiskBucket) Put

func (b *DiskBucket) Put(ctx context.Context, key string, r io.Reader, _ int64, contentType string) error

Put implements Bucket. The file appears complete or not at all.

func (*DiskBucket) Stat

func (b *DiskBucket) Stat(_ context.Context, key string) (Object, error)

Stat implements Bucket.

type InvalidUpload

type InvalidUpload struct {
	Field   string
	Message string
}

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

type Presigner interface {
	PresignGet(key string, ttl time.Duration) (string, error)
}

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 NewS3

func NewS3(o S3Options) (*S3Bucket, error)

NewS3 returns an S3 bucket.

func (*S3Bucket) Delete

func (b *S3Bucket) Delete(ctx context.Context, key string) error

Delete implements Bucket.

func (*S3Bucket) Get

func (b *S3Bucket) Get(ctx context.Context, key string) (io.ReadCloser, Object, error)

Get implements Bucket.

func (*S3Bucket) List

func (b *S3Bucket) List(ctx context.Context, prefix string) ([]Object, error)

List implements Bucket.

func (*S3Bucket) PresignGet

func (b *S3Bucket) PresignGet(key string, ttl time.Duration) (string, error)

PresignGet implements Presigner: a URL that downloads key until ttl passes (at most 7 days).

func (*S3Bucket) PresignPut

func (b *S3Bucket) PresignPut(key string, ttl time.Duration) (string, error)

PresignPut returns a URL that browsers can PUT a file to directly, so large uploads don't pass through the app.

func (*S3Bucket) Put

func (b *S3Bucket) Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error

Put implements Bucket.

func (*S3Bucket) Stat

func (b *S3Bucket) Stat(ctx context.Context, key string) (Object, error)

Stat implements Bucket.

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.

func NewSigner

func NewSigner(secret string) *Signer

NewSigner returns a signer; secret should be at least 32 bytes.

func (*Signer) URL

func (s *Signer) URL(prefix, key string, ttl time.Duration) string

URL returns a link to key under the path prefix Serve uses, valid for ttl.

func (*Signer) Valid

func (s *Signer) Valid(key string, q url.Values) bool

Valid reports whether a link's exp and sig parameters are valid for key.

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

func Receive(ctx *kit.Ctx, field string, opts ReceiveOptions) ([]*Upload, error)

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".

func (*Upload) Open

func (u *Upload) Open() (multipart.File, error)

Open reads the file.

func (*Upload) Save

func (u *Upload) Save(ctx context.Context, b Bucket, key string) error

Save stores the file in b under key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL