Documentation
¶
Index ¶
- Constants
- Variables
- func KindForMime(mime string, overrides map[string]string) string
- func NewPlugin() plugin.Plugin
- func RegisterRoutes(router fiber.Router, db database.Database, config *Config, ...)
- func RegisterStorage(name string, factory StorageFactory)
- type CDNStorage
- func (s *CDNStorage) Delete(ctx context.Context, key string) error
- func (s *CDNStorage) Driver() string
- func (s *CDNStorage) Open(ctx context.Context, key string) (io.ReadCloser, error)
- func (s *CDNStorage) Save(ctx context.Context, key string, r io.Reader, size int64, mimeType string) error
- func (s *CDNStorage) URL(key string) string
- type Config
- type LocalStorage
- func (s *LocalStorage) Delete(_ context.Context, key string) error
- func (s *LocalStorage) Driver() string
- func (s *LocalStorage) Open(_ context.Context, key string) (io.ReadCloser, error)
- func (s *LocalStorage) Save(_ context.Context, key string, r io.Reader, _ int64, _ string) error
- func (s *LocalStorage) URL(string) string
- type Media
- type MediaConverter
- type MediaPlugin
- func (p *MediaPlugin) Dependencies() []string
- func (p *MediaPlugin) GetOpenAPIResources() []plugin.OpenAPIResource
- func (p *MediaPlugin) GetService() *MediaService
- func (p *MediaPlugin) Handler() fiber.Handler
- func (p *MediaPlugin) Initialize(config map[string]any) error
- func (p *MediaPlugin) MigrationDependencies() []string
- func (p *MediaPlugin) MigrationSource() any
- func (p *MediaPlugin) Name() string
- func (p *MediaPlugin) SetupEndpoints(router fiber.Router) error
- type MediaResponseDTO
- type MediaService
- func (s *MediaService) Delete(ctx context.Context, m *Media) error
- func (s *MediaService) GetByID(ctx context.Context, id uuid.UUID) (*Media, error)
- func (s *MediaService) Open(ctx context.Context, m *Media) (io.ReadCloser, error)
- func (s *MediaService) Rename(ctx context.Context, id uuid.UUID, name string) (*Media, error)
- func (s *MediaService) Storage() Storage
- func (s *MediaService) Upload(ctx context.Context, header *multipart.FileHeader, name string, ...) (*Media, error)
- type MediaUpdateDTO
- type Storage
- type StorageFactory
Constants ¶
const ( DriverLocal = "local" DriverCDN = "cdn" )
const ( KindImage = "image" KindVideo = "video" KindAudio = "audio" KindDocument = "document" KindSpreadsheet = "spreadsheet" KindArchive = "archive" KindOther = "other" )
Variables ¶
var ErrFileTooLarge = errors.New("file exceeds maximum allowed size")
Functions ¶
func KindForMime ¶
KindForMime classifies a MIME type into a coarse media kind. Overrides win over the built-ins, so an operator can reclassify or add a format purely via config. An override key ending in "/" matches a whole family.
func RegisterRoutes ¶
func RegisterStorage ¶
func RegisterStorage(name string, factory StorageFactory)
RegisterStorage adds a storage driver under name, overwriting any existing registration. Call it from an init() to teach the plugin a new backend (e.g. an S3 client) without editing this package.
Types ¶
type CDNStorage ¶
type CDNStorage struct {
// contains filtered or unexported fields
}
CDNStorage speaks plain HTTP to an object gateway: PUT to write, GET to read, DELETE to remove. It carries no vendor SDK, so it works against S3-compatible endpoints, a reverse proxy, or any signed-URL gateway the operator points it at.
func NewCDNStorage ¶
func NewCDNStorage(uploadBase, publicBase, authHeader string) *CDNStorage
func (*CDNStorage) Driver ¶
func (s *CDNStorage) Driver() string
func (*CDNStorage) Open ¶
func (s *CDNStorage) Open(ctx context.Context, key string) (io.ReadCloser, error)
func (*CDNStorage) URL ¶
func (s *CDNStorage) URL(key string) string
type Config ¶
type Config struct {
Database database.Database
StorageDriver string `json:"storage_driver" yaml:"storage_driver"`
LocalBasePath string `json:"local_base_path" yaml:"local_base_path"`
// CDNUploadURL is the base URL objects are written to (HTTP PUT/DELETE).
// CDNPublicURL is the base URL objects are read from; when empty it falls
// back to CDNUploadURL. Splitting them supports gateways whose write and
// read hosts differ (e.g. a signed-upload endpoint vs. a public CDN edge).
CDNUploadURL string `json:"cdn_upload_url" yaml:"cdn_upload_url"`
CDNPublicURL string `json:"cdn_public_url" yaml:"cdn_public_url"`
// CDNAuthHeader is sent verbatim as the Authorization header on write
// requests, letting the operator supply a static token without this plugin
// pulling in a vendor SDK.
CDNAuthHeader string `json:"cdn_auth_header" yaml:"cdn_auth_header"`
MaxFileSize int64 `json:"max_file_size" yaml:"max_file_size"`
// AllowedMimeTypes gates uploads by detected MIME type. An empty list
// accepts any type; entries ending in "/" match a whole family (e.g.
// "image/" allows image/png and image/jpeg alike).
AllowedMimeTypes []string `json:"allowed_mime_types" yaml:"allowed_mime_types"`
// KindOverrides maps a MIME type (exact, or a "family/" prefix) to a media
// kind, layered over the built-in defaults. This is the extension point for
// classifying new formats without a code change.
KindOverrides map[string]string `json:"kind_overrides" yaml:"kind_overrides"`
PaginationLimit int `json:"pagination_limit" yaml:"pagination_limit"`
MaxPaginationLimit int `json:"max_pagination_limit" yaml:"max_pagination_limit"`
}
func DefaultConfig ¶
func DefaultConfig() Config
func (*Config) IsAllowedMime ¶
type LocalStorage ¶
type LocalStorage struct {
// contains filtered or unexported fields
}
func NewLocalStorage ¶
func NewLocalStorage(basePath string) (*LocalStorage, error)
func (*LocalStorage) Driver ¶
func (s *LocalStorage) Driver() string
func (*LocalStorage) Open ¶
func (s *LocalStorage) Open(_ context.Context, key string) (io.ReadCloser, error)
func (*LocalStorage) URL ¶
func (s *LocalStorage) URL(string) string
type Media ¶
type Media struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
StorageKey string `json:"storage_key" db:"storage_key"`
StorageDriver string `json:"storage_driver" db:"storage_driver"`
MimeType string `json:"mime_type" db:"mime_type"`
Kind string `json:"kind" db:"kind"`
Extension string `json:"extension" db:"extension"`
Size int64 `json:"size" db:"size"`
Checksum string `json:"checksum" db:"checksum"`
UserID uuid.UUID `json:"user_id" db:"user_id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty" db:"updated_at"`
}
type MediaConverter ¶
type MediaConverter struct {
// contains filtered or unexported fields
}
func NewMediaConverter ¶
func NewMediaConverter(storage Storage) *MediaConverter
func (*MediaConverter) CreateDTOToModel ¶
func (c *MediaConverter) CreateDTOToModel(struct{}) Media
CreateDTOToModel and UpdateDTOToModel exist only to satisfy the processor's ModelConverter interface. Writes never flow through the processor: uploads are multipart (handled by MediaService.Upload) and renames go through MediaService.Rename, so the processor is wired for list/get reads only.
func (*MediaConverter) ModelToResponseDTO ¶
func (c *MediaConverter) ModelToResponseDTO(model Media) MediaResponseDTO
func (*MediaConverter) ModelsToResponseDTOs ¶
func (c *MediaConverter) ModelsToResponseDTOs(models []Media) []MediaResponseDTO
func (*MediaConverter) UpdateDTOToModel ¶
func (c *MediaConverter) UpdateDTOToModel(dto MediaUpdateDTO) Media
type MediaPlugin ¶
type MediaPlugin struct {
// contains filtered or unexported fields
}
func (*MediaPlugin) Dependencies ¶
func (p *MediaPlugin) Dependencies() []string
func (*MediaPlugin) GetOpenAPIResources ¶
func (p *MediaPlugin) GetOpenAPIResources() []plugin.OpenAPIResource
func (*MediaPlugin) GetService ¶
func (p *MediaPlugin) GetService() *MediaService
func (*MediaPlugin) Handler ¶
func (p *MediaPlugin) Handler() fiber.Handler
func (*MediaPlugin) Initialize ¶
func (p *MediaPlugin) Initialize(config map[string]any) error
func (*MediaPlugin) MigrationDependencies ¶
func (p *MediaPlugin) MigrationDependencies() []string
func (*MediaPlugin) MigrationSource ¶
func (p *MediaPlugin) MigrationSource() any
func (*MediaPlugin) Name ¶
func (p *MediaPlugin) Name() string
func (*MediaPlugin) SetupEndpoints ¶
func (p *MediaPlugin) SetupEndpoints(router fiber.Router) error
type MediaResponseDTO ¶
type MediaResponseDTO struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
MimeType string `json:"mime_type"`
Kind string `json:"kind"`
Extension string `json:"extension"`
Size int64 `json:"size"`
Checksum string `json:"checksum"`
URL string `json:"url"`
UserID uuid.UUID `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}
type MediaService ¶
type MediaService struct {
// contains filtered or unexported fields
}
func NewMediaService ¶
func NewMediaService(db database.Database, config *Config, storage Storage) *MediaService
func (*MediaService) Delete ¶
func (s *MediaService) Delete(ctx context.Context, m *Media) error
Delete removes the database row first; the stored bytes are best-effort cleaned up afterward so a storage hiccup can't leave a dangling row that the API still advertises.
func (*MediaService) Open ¶
func (s *MediaService) Open(ctx context.Context, m *Media) (io.ReadCloser, error)
func (*MediaService) Rename ¶
Rename updates only the display name (and updated_at) with a targeted UPDATE. A full-row CRUD update would rewrite the immutable storage columns from a partial DTO and could blank them, so the mutable field is written directly.
func (*MediaService) Storage ¶
func (s *MediaService) Storage() Storage
func (*MediaService) Upload ¶
func (s *MediaService) Upload(ctx context.Context, header *multipart.FileHeader, name string, userID uuid.UUID) (*Media, error)
Upload reads the multipart file fully into memory (bounded by MaxFileSize), sniffs its real MIME type from the bytes rather than trusting the client header, persists the bytes to the storage backend, then records the row. Buffering is deliberate: the checksum and content sniff both need to see the bytes before the write, and the size ceiling keeps the buffer bounded.
type MediaUpdateDTO ¶
type MediaUpdateDTO struct {
Name string `json:"name"`
}
type Storage ¶
type Storage interface {
Driver() string
Save(ctx context.Context, key string, r io.Reader, size int64, mimeType string) error
Open(ctx context.Context, key string) (io.ReadCloser, error)
Delete(ctx context.Context, key string) error
// URL returns a publicly reachable address for the object, or "" when the
// backend has none and bytes must be streamed through the download route.
URL(key string) string
}
Storage is the pluggable backend that holds the raw bytes of a media object. The database only ever stores a key; resolving that key to bytes (or a public URL) is entirely the backend's concern, which is what lets the same media resource live on local disk or a CDN without touching the HTTP layer.