defmodule TempelAws do @moduledoc """ This module is a service module for [tempel](https://bitbucket.org/aripurna/tempel) to handle file persistence using AWS S3 """ @spec init( opts :: %{ aws_access_key: String.t(), aws_secret_key: String.t(), aws_region: String.t(), aws_bucket: String.t() } ) :: map() def init(opts) do access_key = Map.get(opts, :aws_access_key) secret_key = Map.get(opts, :aws_secret_key) region = Map.get(opts, :aws_region) client = AWS.Client.create(access_key, secret_key, region) Map.merge(opts, %{aws_client: client}) end @spec save( file :: %{filename: binary(), content_type: binary(), path: binary()} | %{filename: binary(), content_type: binary(), iodata: binary()}, opts :: map() ) :: {:ok, binary()} | {:error, any()} def save(file, opts) do filename = map_by_atom_or_string(file, :filename) content_type = map_by_atom_or_string(file, :content_type) case extract_file(file) do {:ok, iodata} -> write(iodata, content_type, filename, opts) err -> err end end @spec to_url(opts :: map(), object_key :: binary()) :: String.t() def to_url(opts, object_key) do ttl = map_by_atom_or_string(opts, :ttl) || 1800 bucket = map_by_atom_or_string(opts, :aws_bucket) region = map_by_atom_or_string(opts, :aws_region) access_key = map_by_atom_or_string(opts, :aws_access_key) secret_key = map_by_atom_or_string(opts, :aws_secret_key) uri = URI.parse("https://#{bucket}.s3.#{region}.amazonaws.com/#{AWS.Util.encode_uri(object_key)}") now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second) :aws_signature.sign_v4_query_params( access_key, secret_key, region, "s3", NaiveDateTime.to_erl(now), "GET", URI.to_string(uri), ttl: ttl, body_digest: "UNSIGNED-PAYLOAD" ) end @spec delete(opts :: map(), object_key:: binary()) :: :ok | {:error, reason :: any} def delete(opts, object_key) do client = map_by_atom_or_string(opts, :aws_client) bucket = map_by_atom_or_string(opts, :aws_bucket) case AWS.S3.delete_object(client, bucket, object_key, %{}) do {:ok, _, _} -> :ok other -> other end end defp write(file, content_type, filename, opts) do client = Map.get(opts, :aws_client) bucket = Map.get(opts, :aws_bucket) md5 = :crypto.hash(:md5, file) |> Base.encode64() options = %{"Body" => file, "ContentMD5" => md5, "ContentType" => content_type} case AWS.S3.put_object(client, bucket, filename, options) do {:ok, _, _} -> {:ok, filename} err -> err end end defp extract_file(file) do if path = map_by_atom_or_string(file, :path) do File.read(path) else {:ok, map_by_atom_or_string(file, :iodata)} end end defp map_by_atom_or_string(map, key) do Map.get(map, key) || Map.get(map, Atom.to_string(key)) end end