defmodule ApiWeb.AttachmentController do use ApiWeb, :controller require Logger import Ecto.Query alias Api.Repo def index(conn, params) do decode = """ SELECT convert_from( decrypt(decode('#{params["idstudyattachment"]}', 'hex')::bytea, '1nf0rm3'::bytea, 'aes'), 'UTF8' ); """ idstudyattachment = Repo.query!(decode).rows |> hd() |> hd() |> String.to_integer() [attachment | _] = get_attachment(idstudyattachment) Logger.info("Adjunto: #{inspect(attachment)}") case attachment do # Caso 1: archivo en filesystem %{path: path} when not is_nil(path) -> if File.exists?(path) do conn |> put_resp_content_type("application/#{attachment[:format]}") |> put_resp_header("content-disposition", "inline; filename=#{attachment[:fullname]}") |> send_file(200, path) else conn |> send_resp(404, "File not found") end # Caso 2: archivo en S3 (zip → redirige) %{s3_path: s3_path, format: "zip"} when not is_nil(s3_path) -> conn |> redirect(external: s3_path) # Caso 3: archivo en S3 (otros formatos → preview o descarga) %{s3_path: s3_path} when not is_nil(s3_path) -> redirect_or_preview(conn, s3_path, attachment[:fullname]) # Caso 4: ni path ni s3_path → usar campo attachment (bytea) %{path: nil, s3_path: nil, attachment: bin} when not is_nil(bin) -> content_type = content_type_for(attachment[:format]) disposition = if content_type == "application/octet-stream" do "attachment" else "inline" end conn |> put_resp_content_type(content_type) |> put_resp_header( "content-disposition", "#{disposition}; filename=#{attachment[:fullname]}" ) |> send_resp(200, bin) # Caso default _ -> conn |> send_resp(400, "Invalid attachment") end end def get_attachment(idstudyattachment) do query = from sa in "studyattachments", where: sa.idstudyattachment == ^idstudyattachment, select: %{ attachment: sa.attachment, path: sa.path, s3_path: sa.s3_path, format: sa.format, fullname: fragment("concat(name, '.', format)") } Repo.all(query) end defp redirect_or_preview(conn, path, fullname) do case HTTPoison.get(path, [], [follow_redirect: false, hackney: [pool: :default]]) do {:ok, %HTTPoison.Response{status_code: 302, headers: headers}} -> handle_redirect(conn, headers, fullname) {:ok, %HTTPoison.Response{status_code: 200, body: body, headers: file_headers}} -> preview_attachment(conn, body, file_headers, fullname) {:error, reason} -> Logger.error("Error al obtener el adjunto: #{inspect(reason)}") conn |> send_resp(404, "File not found") end end defp handle_redirect(conn, headers, fullname) do case List.keyfind(headers, "location", 0) do {"location", redirected_url} -> Logger.info("Redirigiendo a: #{redirected_url}") case HTTPoison.get(redirected_url, [], [follow_redirect: true]) do {:ok, %HTTPoison.Response{status_code: 200, body: body, headers: file_headers}} -> preview_attachment(conn, body, file_headers, fullname) {:error, reason} -> Logger.error("Error al descargar el adjunto tras redirección: #{inspect(reason)}") conn |> send_resp(404, "File not found") end _ -> Logger.error("Redirección sin ubicación válida.") conn |> send_resp(404, "File not found") end end defp preview_attachment(conn, body, file_headers, fullname) do content_type = get_content_type(file_headers) conn |> put_resp_content_type(content_type) |> put_resp_header("content-disposition", "inline; filename=#{fullname}") |> send_resp(200, body) end defp get_content_type(headers) do case List.keyfind(headers, "content-type", 0) do {"content-type", content_type} -> content_type _ -> "application/octet-stream" end end defp content_type_for(format) do case String.downcase(format || "") do "pdf" -> "application/pdf" "jpg" -> "image/jpeg" "jpeg" -> "image/jpeg" "png" -> "image/png" _ -> "application/octet-stream" end end end