Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions lib/mave_core/embeds.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1404,14 +1404,21 @@ defmodule MaveCore.Embeds do
end

defp preview_poster_url(%Space{} = space, %Embed{} = embed, settings, cache_buster) do
private_upload? = uploaded_poster_settings?(settings) and MaveCore.Playback.protected?(embed)

url =
if generated_thumbnail_poster_settings?(settings) and not is_nil(cache_buster) do
thumbnail_url(space, embed, cache_buster)
else
SettingsSerializer.preview_poster_url(space, embed, settings)
cond do
private_upload? ->
thumbnail_url(space, embed, cache_buster)

generated_thumbnail_poster_settings?(settings) and not is_nil(cache_buster) ->
thumbnail_url(space, embed, cache_buster)

true ->
SettingsSerializer.preview_poster_url(space, embed, settings)
end

if uploaded_poster_settings?(settings) do
if uploaded_poster_settings?(settings) and not private_upload? do
with_cache_buster(url, cache_buster)
else
url
Expand Down
6 changes: 6 additions & 0 deletions lib/mave_core/media/storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ defmodule MaveCore.Media.Storage do
|> handle_response()
end

@doc "Lists current object keys under a prefix, following all result pages."
def list_keys(bucket, prefix, storage_profile \\ nil)
when is_binary(bucket) and is_binary(prefix) do
list_prefix_keys(bucket, prefix, storage_profile)
end

@doc "Fetches at most `max_bytes` from the beginning of an object."
def get_prefix(bucket, path, storage_profile, max_bytes)
when is_binary(bucket) and is_binary(path) do
Expand Down
19 changes: 19 additions & 0 deletions test/mave_core/embeds_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2399,6 +2399,25 @@ defmodule MaveCore.EmbedsTest do
assert payload.thumbnail == versioned_thumbnail_url
assert payload.preview_poster == versioned_thumbnail_url
assert payload.snippet_player_poster == versioned_thumbnail_url

private_video =
video
|> Ecto.Changeset.change(playback_visibility: :private, playback_status: :private)
|> Repo.update!()

private_payload = Embeds.get_video_dashboard_payload(space, private_video)
uri = URI.parse(private_payload.preview_poster)
query = URI.decode_query(uri.query)

assert private_payload.preview_poster == private_payload.thumbnail
assert String.ends_with?(uri.path, "/#{video.hash}/thumbnail.jpg")
assert query["X-Amz-Signature"]
assert query["X-Amz-Expires"] == "86400"
assert query["response-cache-control"] == "private, max-age=300"
assert query["e"] == to_string(DateTime.to_unix(inserted_at, :microsecond))
assert length(URI.query_decoder(uri.query) |> Enum.filter(fn {key, _} -> key == "e" end)) == 1
refute private_payload.snippet_player_poster =~ "X-Amz-Signature"
assert private_video.settings.external_poster == thumbnail_url
end

test "dashboard video rows cache-bust completed custom thumbnail URLs" do
Expand Down
36 changes: 36 additions & 0 deletions test/mave_core/media/storage_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@ defmodule MaveCore.Media.StorageTest do

setup {Req.Test, :verify_on_exit!}

test "list_keys follows object-store pagination without guessing version prefixes" do
Req.default_options(plug: {Req.Test, __MODULE__})

profile = [
endpoint: "https://storage.example.test",
access_key_id: "key",
secret_access_key: "secret"
]

Req.Test.expect(__MODULE__, fn conn ->
conn = fetch_query_params(conn)
assert conn.query_params["prefix"] == "embed/"
refute conn.query_params["continuation-token"]

send_resp(
conn,
200,
"<ListBucketResult><Contents><Key>embed/manifest.json</Key></Contents><IsTruncated>true</IsTruncated><NextContinuationToken>next-page</NextContinuationToken></ListBucketResult>"
)
end)

Req.Test.expect(__MODULE__, fn conn ->
conn = fetch_query_params(conn)
assert conn.query_params["continuation-token"] == "next-page"

send_resp(
conn,
200,
"<ListBucketResult><Contents><Key>embed/v8/player.html</Key></Contents><IsTruncated>false</IsTruncated></ListBucketResult>"
)
end)

assert {:ok, ["embed/manifest.json", "embed/v8/player.html"]} =
Storage.list_keys("bucket", "embed/", profile)
end

test "signed reads support a full dashboard day while remaining bounded" do
profile = [
endpoint: "https://storage.example.test",
Expand Down
31 changes: 31 additions & 0 deletions test/mave_core_web/live/dashboard/videos/show_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,37 @@ defmodule MaveCoreWeb.Live.Dashboard.Videos.ShowTest do
assert html =~ versioned_thumbnail_url
end

@tag :private_playback_ui
test "private custom thumbnails use signed URLs in the player and upload preview", %{conn: conn} do
{conn, space} = authenticated_conn(conn)
video = video_embed_fixture(space, %{name: "Private custom thumbnail"})

thumbnail_url =
SettingsSerializer.storage_object_url("space-#{space.hash}", "#{video.hash}/thumbnail.jpg")

{:ok, video} =
MaveCore.Embeds.update_settings(video, %{poster: :upload, external_poster: thumbnail_url})

video
|> Ecto.Changeset.change(playback_visibility: :private, playback_status: :private)
|> Repo.update!()

{:ok, view, _html} = live(conn, "/videos/#{video.id}")

assert has_element?(
view,
~s(mave-player[poster*="/#{video.hash}/thumbnail.jpg?"][poster*="X-Amz-Signature="])
)

assert has_element?(
view,
~s(#droparea[style*="/#{video.hash}/thumbnail.jpg?"][style*="X-Amz-Signature="])
)

refute has_element?(view, ~s(mave-player[poster="#{thumbnail_url}"]))
refute has_element?(view, "#snippet-code", "X-Amz-Signature")
end

@tag :poster_upload
test "poster processing remains visible until a delayed conversion completes", %{conn: conn} do
{conn, space} = authenticated_conn(conn)
Expand Down
Loading