Media, made durable.
Phoenix/Ecto-native media lifecycle library. Rindle owns the durable work that happens after upload: session tracking, verification, asset state, variants, background processing, signed delivery, and cleanup.
README.md is the narrow quickstart. guides/getting_started.md
is the canonical deep adopter guide for the same first-run path.
Add Rindle to your deps:
def deps do
[
{:rindle, "~> 0.1"}
]
endIf you use the S3 adapter, also choose an ExAws HTTP client. :hackney is the
most-tested path in this repo:
def deps do
[
{:rindle, "~> 0.1"},
{:hackney, "~> 1.20"}
]
endRun mix deps.get.
Rindle persists through your adopter-owned Repo. Configure that explicitly:
config :rindle, :repo, MyApp.RepoRindle also requires the default Oban path for background work. Adopters own
the Oban supervision tree, queue config, and default Oban Repo:
config :my_app, Oban,
repo: MyApp.Repo,
queues: [
rindle_promote: 5,
rindle_process: 10,
rindle_purge: 2,
rindle_maintenance: 1
]Run your host app migrations and the packaged Rindle migrations explicitly. The
consumer smoke lane proves this Application.app_dir/2 path from a generated
Phoenix app:
rindle_path = Application.app_dir(:rindle, "priv/repo/migrations")
host_path = Path.join([File.cwd!(), "priv", "repo", "migrations"])
{:ok, _, _} =
Ecto.Migrator.with_repo(MyApp.Repo, fn repo ->
for path <- [host_path, rindle_path] do
Ecto.Migrator.run(repo, path, :up, all: true)
end
end)Rindle does not ship a public mix rindle.* install task for this in v1.1.
The public path is the docs snippet above; the repo-private automation lives in
the install smoke harness.
The first-run path is direct upload by presigned PUT. Multipart upload is supported, but it is an advanced capability and not the default onboarding story.
alias Rindle.Upload.Broker
{:ok, session} =
Broker.initiate_session(MyApp.MediaProfile, filename: "photo.png")
{:ok, %{session: signed, presigned: presigned}} =
Broker.sign_url(session.id)
# your client PUTs bytes to presigned.url
{:ok, %{session: completed, asset: asset}} =
Broker.verify_completion(session.id)
{:ok, signed_url} =
Rindle.Delivery.url(MyApp.MediaProfile, asset.storage_key)That is the same public path proven by the built-artifact install smoke and the canonical adopter lifecycle test.
guides/getting_started.md: canonical deep adopter guide for Repo ownership, Oban ownership, migrations, profile setup, and the same presigned PUT lifecycleguides/background_processing.md: default Oban ownership and queue detailsguides/storage_capabilities.md: capability boundaries, including multipart as an advanced path
For local GSD cleanup, run mix gsd.clean. It removes known transient outputs,
prunes stale worktree metadata, and reports any remaining .planning/ dirt
without deleting tracked planning artifacts.
Use the GSD workflows for the tracked planning lifecycle:
$gsd-complete-milestonewhen a milestone is actually done$gsd-cleanupto archive completed milestone phase directories$gsd-pr-branchto prepare a review branch without.planning/commits
MIT