Skip to content

Commit aa7f569

Browse files
committed
Passthrough ACL option
1 parent 6713a47 commit aa7f569

File tree

3 files changed

+88
-28
lines changed

3 files changed

+88
-28
lines changed

lib/upload/changeset.ex

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
defmodule Upload.Changeset do
22
@moduledoc """
3-
Functions for use with changesets to upload and validate attachments.
3+
Functions for use with changesets to cast and validate attachments.
4+
5+
These functions will not perform an upload of the `Upload.Blob` as they only
6+
create and validate the database record. For synchronizing uploads remotely
7+
see `Upload.Multi.handle_changes/6`
48
59
```elixir
610
schema "people" do

lib/upload/multi.ex

+61-23
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,45 @@ defmodule Upload.Multi do
1212

1313
@doc """
1414
Upload a blob to storage.
15+
16+
## Options
17+
18+
- `canned_acl` - The canned ACL to use with S3 if using S3 as the storage
19+
backend.
20+
1521
"""
16-
@spec upload_blob(Multi.t(), Multi.name(), Blob.t()) :: Multi.t()
17-
def upload_blob(multi, name, %Blob{key: key, path: path} = blob)
18-
when is_binary(key) and is_binary(path) do
19-
Multi.run(multi, name, fn _repo, _ctx -> do_upload_blob(blob) end)
20-
end
22+
def upload_blob(multi, name, blob, opts \\ [])
2123

2224
@spec upload_blob(Multi.t(), Multi.name(), Blob.t()) :: Multi.t()
23-
def upload_blob(multi, name, %Blob{key: key, path: path} = blob)
25+
def upload_blob(multi, name, %Blob{key: key, path: path} = blob, opts)
2426
when is_binary(key) and is_binary(path) do
25-
Multi.run(multi, name, fn _repo, _ctx -> do_upload_blob(blob) end)
27+
Multi.run(multi, name, fn _repo, _ctx -> do_upload_blob(blob, opts) end)
2628
end
2729

2830
@spec upload_blob(Multi.t(), Multi.name(), Multi.fun(Blob.t())) :: Multi.t()
29-
def upload_blob(multi, name, fun) when is_function(fun) do
30-
Multi.run(multi, name, fn _repo, ctx -> ctx |> fun.() |> do_upload_blob() end)
31+
def upload_blob(multi, name, fun, opts) when is_function(fun) do
32+
Multi.run(multi, name, fn _repo, ctx -> ctx |> fun.() |> do_upload_blob(opts) end)
3133
end
3234

33-
defp do_upload_blob(nil), do: {:ok, nil}
34-
defp do_upload_blob(%NotLoaded{} = blob), do: {:ok, blob}
35-
defp do_upload_blob(%Blob{path: nil} = blob), do: {:ok, blob}
35+
defp do_upload_blob(nil, _opts), do: {:ok, nil}
36+
defp do_upload_blob(%NotLoaded{} = blob, _opts), do: {:ok, blob}
37+
defp do_upload_blob(%Blob{path: nil} = blob, _opts), do: {:ok, blob}
3638

37-
defp do_upload_blob(%Blob{path: path, key: key} = blob) when is_binary(key) do
39+
defp do_upload_blob(%Blob{path: path, key: key} = blob, opts) when is_binary(key) do
3840
Upload.Logger.info("Uploading #{key}")
3941

4042
with :ok <- Storage.upload(path, key),
43+
:ok <- Upload.put_access_control_list(blob, opts[:canned_acl] || :private),
4144
do: {:ok, blob}
4245
end
4346

4447
@doc """
4548
Upload multiple variants as part of an `Ecto.Multi`.
49+
50+
## Options
51+
52+
- `canned_acl` - The canned ACL to use with S3 if using S3 as the storage
53+
backend.
4654
"""
4755
def upload_variants(multi, name, fun, variants, transform_fn, opts \\ [])
4856
when is_function(fun) do
@@ -70,7 +78,8 @@ defmodule Upload.Multi do
7078
7179
If the `field` change is `nil` in the changeset, it will be deleted remotely
7280
and in your database. If the `field` change in the changeset is an uploadable
73-
type such as a `Plug.Upload` or file path, it will be uploaded.
81+
type such as a `Plug.Upload` or file path, it will be uploaded, replacing any
82+
existing associated upload.
7483
7584
## Example
7685
@@ -82,6 +91,11 @@ defmodule Upload.Multi do
8291
|> Repo.transaction()
8392
end
8493
```
94+
95+
## Options
96+
97+
- `canned_acl` - The canned ACL to use with S3 if using S3 as the storage
98+
backend.
8599
"""
86100
def handle_changes(multi, name, subject, changeset, field, opts \\ []) do
87101
key_function = key_function_from_opts(opts)
@@ -110,7 +124,7 @@ defmodule Upload.Multi do
110124
record_changeset.changes
111125
|> Enum.reduce(Ecto.Multi.new(), fn {changed_field, change}, multi ->
112126
# Deletes if the change is 'nil', uploads otherwise.
113-
handle_change({changed_field, change}, multi, changeset)
127+
handle_change({changed_field, change}, multi, changeset, opts)
114128
end)
115129
|> Multi.update("#{field}_attach_blob", record_changeset)
116130
|> repo.transaction()
@@ -137,7 +151,7 @@ defmodule Upload.Multi do
137151

138152
# We're setting the upload field to nil so let's check
139153
# if the existing field is set and delete it if so.
140-
defp handle_change({field, nil}, multi, changeset) do
154+
defp handle_change({field, nil}, multi, changeset, _opts) do
141155
case Map.get(changeset.data, field) do
142156
%Upload.Blob{} = blob -> delete_blob(multi, :delete_blob, blob)
143157
_ -> multi
@@ -146,12 +160,12 @@ defmodule Upload.Multi do
146160

147161
# We're setting the upload field so let's check
148162
# if the existing field is set and delete it if so.
149-
defp handle_change({field, change}, multi, changeset) do
150-
multi = handle_change({field, nil}, multi, changeset)
163+
defp handle_change({field, change}, multi, changeset, opts) do
164+
multi = handle_change({field, nil}, multi, changeset, opts)
151165

152166
blob = Ecto.Changeset.apply_changes(change)
153167

154-
upload_blob(multi, field, blob)
168+
upload_blob(multi, field, blob, opts)
155169
end
156170

157171
@doc """
@@ -221,12 +235,20 @@ defmodule Upload.Multi do
221235
@doc """
222236
Creates and uploads a single variant of an `Upload.Blob` inside of an `Ecto.Multi`.
223237
238+
## Example
239+
224240
```elixir
225241
Ecto.Multi.new()
226242
|> Ecto.Multi.insert(:person, changeset)
227243
|> Upload.Multi.handle_changes(:upload_avatar, :person, changeset, :avatar, key_function: key_function)
228244
|> Upload.Multi.create_variant(fn ctx -> ctx.person.avatar end, :small, transform_fn: &transform_fn/3)
229245
```
246+
247+
## Options
248+
249+
- `canned_acl` - The canned ACL to use with S3 if using S3 as the storage
250+
backend.
251+
230252
"""
231253
def create_variant(multi, fun, variant, transform_fn, opts)
232254
when is_function(fun, 1) and is_function(transform_fn, 3) do
@@ -257,7 +279,8 @@ defmodule Upload.Multi do
257279
original_blob,
258280
variant,
259281
transform_fn,
260-
format
282+
format,
283+
opts
261284
)
262285
end)
263286
end
@@ -267,12 +290,19 @@ defmodule Upload.Multi do
267290
@doc """
268291
Creates and uploads multiple variants of an `Upload.Blob` inside of an `Ecto.Multi`.
269292
293+
## Example
294+
270295
```elixir
271296
Ecto.Multi.new()
272297
|> Ecto.Multi.insert(:person, changeset)
273298
|> Upload.Multi.handle_changes(:upload_avatar, :person, changeset, :avatar, key_function: key_function)
274299
|> Upload.Multi.create_variants(fn ctx -> ctx.person.avatar end, [:small, :large], transform_fn: &transform_fn/3)
275300
```
301+
302+
## Options
303+
304+
- `canned_acl` - The canned ACL to use with S3 if using S3 as the storage
305+
backend.
276306
"""
277307
def create_variants(multi, fun, variants, transform_fn, opts)
278308
when is_function(fun, 1) and is_function(transform_fn, 3) do
@@ -305,21 +335,29 @@ defmodule Upload.Multi do
305335
original_blob,
306336
variant,
307337
transform_fn,
308-
format
338+
format,
339+
opts
309340
)
310341
end)
311342
end)
312343
end
313344

314-
defp download_and_insert_variant(multi, original_blob, variant, transform_fn, format) do
345+
defp download_and_insert_variant(
346+
multi,
347+
original_blob,
348+
variant,
349+
transform_fn,
350+
format,
351+
opts
352+
) do
315353
Multi.run(multi, "download_and_insert_#{variant}_#{format}", fn repo, _ ->
316354
with {:ok, blob_path} <- create_random_file(),
317355
:ok <- download_file(original_blob.key, blob_path),
318356
{:ok, variant_path} <-
319357
call_transform_fn(transform_fn, blob_path, variant, format),
320358
:ok <- cleanup(blob_path),
321359
{:ok, blob} <- insert_variant(repo, original_blob, variant, variant_path),
322-
{:ok, _} <- do_upload_blob(blob),
360+
{:ok, _} <- do_upload_blob(blob, opts),
323361
:ok <- cleanup(variant_path) do
324362
{:ok, blob}
325363
else

test/upload/multi_test.exs

+22-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,23 @@ defmodule Upload.MultiTest do
2222
assert person.avatar.key in list_uploaded_keys()
2323
end
2424

25-
test "overwrites deletes old blobs" do
25+
test "sets the ACL to public" do
26+
assert {:ok, person} = insert_person(%{avatar: @upload})
27+
assert person.avatar_id
28+
29+
with_mock(Storage, [:passthrough], put_access_control_list: fn _key, _acl -> :ok end) do
30+
{:ok, person} = update_person(person, %{avatar: @upload})
31+
32+
assert person.avatar
33+
assert person.avatar.key == "uploads/users/#{person.id}/avatar.jpg"
34+
35+
assert_called(
36+
Storage.put_access_control_list("uploads/users/#{person.id}/avatar.jpg", acl: :public)
37+
)
38+
end
39+
end
40+
41+
test "overwrites and deletes old blobs" do
2642
{:ok, person} = insert_person(%{avatar: @upload})
2743

2844
assert person.avatar_id
@@ -38,7 +54,7 @@ defmodule Upload.MultiTest do
3854
end
3955
end
4056

41-
describe "handle_changes/2" do
57+
describe "handle_changes/6" do
4258
test "can use the ID in the upload key from a record created from the multi itself" do
4359
changeset =
4460
%Person{}
@@ -222,7 +238,8 @@ defmodule Upload.MultiTest do
222238
|> Upload.Multi.handle_changes(:person, :insert_person, changeset, :avatar,
223239
key_function: fn user ->
224240
"uploads/users/#{user.id}/avatar"
225-
end
241+
end,
242+
canned_acl: :public
226243
)
227244
|> Repo.transaction()
228245
|> case do
@@ -237,7 +254,8 @@ defmodule Upload.MultiTest do
237254
Ecto.Multi.new()
238255
|> Ecto.Multi.update(:update_person, changeset)
239256
|> Upload.Multi.handle_changes(:person, :update_person, changeset, :avatar,
240-
key_function: &key_function/1
257+
key_function: &key_function/1,
258+
canned_acl: :public
241259
)
242260
|> Repo.transaction()
243261
|> case do

0 commit comments

Comments
 (0)