Skip to content

Commit

Permalink
fix: Finishing tests. #69
Browse files Browse the repository at this point in the history
  • Loading branch information
LuchoTurtle committed Jun 28, 2023
1 parent 1490d08 commit 317f1be
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 76 deletions.
97 changes: 51 additions & 46 deletions lib/app/upload.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,23 @@ defmodule App.Upload do
filename: "my-awesome-image.png"
}
Uploads to `AWS S3` using `ExAws.S3.upload` and returns the result.
Note: this function follows the "Let it crash" philosophy.
Hence the "try rescue" block in the ApiController.
If the upload fails it will throw an error; we *want* that.
Log it and address it when it arises.
If the upload fails for whatever reason (invalid content type, invalid CID, request to S3 fails),
the an error is returned `{:error, reason}`.
"""
def upload(image) do
# Create `CID` from file contents so filenames are unique
case File.read(image.path) do
{:ok, file_binary} ->
dbg(file_binary)
file_cid = Cid.cid(file_binary)

dbg(file_cid)

file_extension =
image.content_type
|> MIME.extensions()
|> List.first()

dbg(file_extension)

# Check if file `cid` and extension are valid.
case {file_cid, file_extension} do
{"invalid data type", nil} ->
dbg("ay")
{:error, :invalid_extension_and_cid}

{"invalid data type", _extension} ->
Expand All @@ -47,50 +39,63 @@ defmodule App.Upload do
{_cid, nil} ->
{:error, :invalid_extension}


# If the `cid` and extension are valid, we are safe to upload
{file_cid, file_extension} ->
# Creating filename with the retrieved extension
file_name = "#{file_cid}.#{file_extension}"

# Upload to S3
{:ok, body} =
image.path
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(Application.get_env(:ex_aws, :original_bucket), file_name,
acl: :public_read,
content_type: image.content_type
)
|> ExAws.request(get_ex_aws_request_config_override())
request_response =
try do
image.path
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(Application.get_env(:ex_aws, :original_bucket), file_name,
acl: :public_read,
content_type: image.content_type
)
|> ExAws.request(get_ex_aws_request_config_override())
rescue
_e ->
{:error, :upload_fail}
end

# Check if the request was successful
case request_response do
# If the request was successful, we parse the result
{:ok, body} ->
# Sample response:
# %{
# body: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n
# <CompleteMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">
# <Location>https://s3.eu-west-3.amazonaws.com/imgup-original/qvWtbC7WaT.jpg</Location>
# <Bucket>imgup-original</Bucket><Key>qvWtbC7WaT.jpg</Key>
# <ETag>\"4ecd62951576b7e5b4a3e869e5e98a0f-1\"</ETag></CompleteMultipartUploadResult>",
# headers: [
# {"x-amz-id-2",
# "wNTNZKt82vgnOuT1o2Tz8z3gcRzd6wXofYxQmBUkGbBGTpmv1WbwjjGiRAUtOTYIm92bh/VJHhI="},
# {"x-amz-request-id", "QRENBY1MJTQWD7CZ"},
# {"Date", "Tue, 13 Jun 2023 10:22:44 GMT"},
# {"x-amz-expiration",
# "expiry-date=\"Thu, 15 Jun 2023 00:00:00 GMT\", rule-id=\"delete-after-1-day\""},
# {"x-amz-server-side-encryption", "AES256"},
# {"Content-Type", "application/xml"},
# {"Transfer-Encoding", "chunked"},
# {"Server", "AmazonS3"}
# ],
# status_code: 200
# }

# Sample response:
# %{
# body: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n
# <CompleteMultipartUploadResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">
# <Location>https://s3.eu-west-3.amazonaws.com/imgup-original/qvWtbC7WaT.jpg</Location>
# <Bucket>imgup-original</Bucket><Key>qvWtbC7WaT.jpg</Key>
# <ETag>\"4ecd62951576b7e5b4a3e869e5e98a0f-1\"</ETag></CompleteMultipartUploadResult>",
# headers: [
# {"x-amz-id-2",
# "wNTNZKt82vgnOuT1o2Tz8z3gcRzd6wXofYxQmBUkGbBGTpmv1WbwjjGiRAUtOTYIm92bh/VJHhI="},
# {"x-amz-request-id", "QRENBY1MJTQWD7CZ"},
# {"Date", "Tue, 13 Jun 2023 10:22:44 GMT"},
# {"x-amz-expiration",
# "expiry-date=\"Thu, 15 Jun 2023 00:00:00 GMT\", rule-id=\"delete-after-1-day\""},
# {"x-amz-server-side-encryption", "AES256"},
# {"Content-Type", "application/xml"},
# {"Transfer-Encoding", "chunked"},
# {"Server", "AmazonS3"}
# ],
# status_code: 200
# }
# Fetch the contents of the returned XML string from `ex_aws`.
# This XML is parsed with `sweet_xml`:
# github.com/kbrw/sweet_xml#the-x-sigil
url = body.body |> xpath(~x"//text()") |> List.to_string()
compressed_url = "#{@compressed_baseurl}#{file_name}"
{:ok, %{url: url, compressed_url: compressed_url}}

# Fetch the contents of the returned XML string from `ex_aws`.
# This XML is parsed with `sweet_xml`:
# github.com/kbrw/sweet_xml#the-x-sigil
url = body.body |> xpath(~x"//text()") |> List.to_string()
compressed_url = "#{@compressed_baseurl}#{file_name}"
{:ok, %{url: url, compressed_url: compressed_url}}
# If the request was unsuccessful, throw an error
{:error, _reason} ->
{:error, :upload_fail}
end
end

{:error, _reason} ->
Expand Down
59 changes: 29 additions & 30 deletions lib/app_web/controllers/api_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,42 @@ defmodule AppWeb.ApiController do
require Logger

def create(conn, %{"" => params}) do
# dbg(params)
# check if content_type e.g: "image/png"

# Check if content_type e.g: "image/png"
if String.contains?(params.content_type, "image") do
try do
case App.Upload.upload(params) do
{:ok, body} ->
render(conn, :success, body)

{:error, :failure_read} ->
render(conn |> put_status(400), %{body: "Error uploading file. Failure reading file."})

{:error, :invalid_cid} ->
render(conn |> put_status(400), %{
body: "Error uploading file. Failure creating the CID filename."
})

{:error, :invalid_extension} ->
render(conn |> put_status(400), %{
body: "Error uploading file. Failure parsing the file extension."
})

{:error, :invalid_extension_and_cid} ->
render(conn |> put_status(400), %{
body: "Error uploading file. The file extension and contents are invalid."
})
end
rescue
e ->
Logger.error(Exception.format(:error, e, __STACKTRACE__))
render(conn |> put_status(400), %{body: "Error uploading file #26"})
case App.Upload.upload(params) do
{:ok, body} ->
render(conn, :success, body)

{:error, :failure_read} ->
render(conn |> put_status(400), %{body: "Error uploading file. Failure reading file."})

{:error, :invalid_cid} ->
render(conn |> put_status(400), %{
body: "Error uploading file. Failure creating the CID filename."
})

{:error, :invalid_extension} ->
render(conn |> put_status(400), %{
body: "Error uploading file. Failure parsing the file extension."
})

{:error, :invalid_extension_and_cid} ->
render(conn |> put_status(400), %{
body: "Error uploading file. The file extension and contents are invalid."
})

{:error, :upload_fail} ->
render(conn |> put_status(400), %{
body: "Error uploading file. There was an error uploading the file to S3."
})
end
else
render(conn |> put_status(400), %{body: "Uploaded file is not a valid image."})
end
end

# preserve backward compatibility with "image" keyword:
# Preserve backward compatibility with "image" keyword:
def create(conn, %{"image" => image}) do
# dbg(image)
create(conn, %{"" => image})
Expand Down
11 changes: 11 additions & 0 deletions test/app_web/api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,15 @@ defmodule AppWeb.APITest do
}
end
end

test "valid file but the upload to S3 failed. It should return an error.", %{conn: conn} do

with_mock ExAws, [request: fn(_input) -> {:error, :failure} end] do
conn = post(conn, ~p"/api/images", @valid_image_attrs)

assert Map.get(Jason.decode!(response(conn, 400)), "errors") == %{
"detail" => "Error uploading file. There was an error uploading the file to S3."
}
end
end
end

0 comments on commit 317f1be

Please sign in to comment.