Files
api-v2/lib/api_web/controllers/attachment_controller.ex
2025-08-22 10:21:47 -03:00

143 lines
4.4 KiB
Elixir

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