uploader

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: May 5, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package uploader provides HTTP-layer helpers for file uploads built on top of den.Storage: an Uploader service that validates and persists multipart uploads into a document.Attachment, a ServeHandler that streams stored bytes back to HTTP clients, and a Mount helper that registers the serving handler at the Storage's own URL prefix.

The package does not implement a burrow.App. The application owns the routing:

u := uploader.NewUploader(db)
uploader.Mount(r, db.Storage())
r.Post("/upload", func(w http.ResponseWriter, r *http.Request) {
    att, err := u.Store(r, "file", uploader.StoreOptions{
        AllowedTypes: []string{"image/"},
        MaxSize:      10 << 20,
    })
    // ...
})

All Storage configuration — root directory, URL prefix, backend choice — lives on the den.Storage passed to den.WithStorage at DB open time. The URL prefix is written once (at Storage construction) and read by both den.Storage.URL (for template URL generation, exposed globally via the `mediaURL` template function) and Mount (for route registration).

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTypeNotAllowed = errors.New("uploader: file type not allowed")
	ErrFileTooLarge   = errors.New("uploader: file too large")
	ErrMissingField   = errors.New("uploader: missing form field")
)

Sentinel errors for upload validation.

Empty uploads surface as storage.ErrEmptyContent — the same sentinel a Storage backend returns when a zero-byte reader reaches it. That lets handler code check for "empty upload" uniformly whether the emptiness was caught at the HTTP layer or inside the backend.

Functions

func Mount

func Mount(r chi.Router, s den.Storage)

Mount registers a ServeHandler on r at the URL prefix the Storage advertises via URLPrefix(). Writes the prefix only once at Storage construction — routing derives from it. Safe to call with any den.Storage: backends that don't implement URLPrefix (remote backends serving via absolute URLs) are no-ops.

fs, _ := file.New("data/media", "/media/")
uploader.Mount(r, fs) // registers "/media/*" → ServeHandler(fs)

func ServeHandler

func ServeHandler(s den.Storage) http.Handler

ServeHandler returns an http.Handler that streams bytes from the Storage, with aggressive caching (Cache-Control immutable max-age=1y). Files are content-addressed so a changed file gets a new URL, which makes the long cache safe.

Treats the incoming request's URL path as the storage key directly, so callers must strip any mount prefix first. Mount does this automatically via chi.Router.Mount; hand-routed callers wrap with http.StripPrefix (or chi's Mount) themselves.

404s use http.NotFound (stdlib) rather than burrow's HTTPError + RenderError chain — asset 404s reach the user as a broken image or a failed XHR, not as a navigated page, so the framework's HTML error page doesn't apply. Apps that need custom asset-404 behaviour should wrap this handler in their own middleware.

Types

type StoreOptions

type StoreOptions struct {
	// AllowedTypes restricts uploads to a MIME allow-list. An empty
	// slice allows all types. Matching is prefix-based so entries like
	// "image/" broaden to any image subtype.
	AllowedTypes []string

	// MaxSize is the per-file upper bound in bytes. Zero means no limit.
	// When the limit is exceeded the upload is aborted mid-stream with
	// ErrFileTooLarge — no partial file is persisted.
	MaxSize int64

	// Filename is the original filename, used as a fallback when the
	// detected MIME type has no mapped extension. Populated from the
	// multipart header automatically if left empty.
	Filename string
}

StoreOptions configures a single Uploader.Store invocation.

type Uploader

type Uploader struct {
	// contains filtered or unexported fields
}

Uploader binds a den.Storage to the HTTP ingress helpers. Construct once per server with NewUploader and reuse across handlers.

func NewUploader

func NewUploader(db *den.DB) *Uploader

NewUploader returns an Uploader that stores files into the Storage installed on db via den.WithStorage. Panics when the db has no Storage configured — that state always indicates a setup bug the program cannot recover from.

func (*Uploader) Storage

func (u *Uploader) Storage() den.Storage

Storage returns the bound backend. Useful for compensating deletes on record-save errors: `_ = u.Storage().Delete(ctx, att)`.

func (*Uploader) Store

func (u *Uploader) Store(r *http.Request, fieldName string, opts StoreOptions) (document.Attachment, error)

Store extracts a file from a multipart request, validates it (MIME allow-list, max size, non-empty), and persists it through the bound Storage. The returned document.Attachment is ready to assign onto a document field (embedded or named) before the subsequent den.Insert / den.Update.

Typical IS-a-file usage (the document IS the file):

att, err := u.Store(r, "file", opts)
if err != nil { return err }
media := &Media{Attachment: att, AltText: "..."}
if err := den.Insert(r.Context(), db, media); err != nil {
    // Insert failed after bytes were stored — clean up to avoid
    // an orphan. Best-effort, log if needed.
    _ = u.Storage().Delete(r.Context(), att)
    return err
}

Typical HAS-file usage (the document points at a file):

att, err := u.Store(r, "hero", opts)
if err != nil { return err }
post.Hero = att
if err := den.Update(r.Context(), db, post); err != nil {
    _ = u.Storage().Delete(r.Context(), att)
    return err
}

Jump to

Keyboard shortcuts

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