Se crea el parser para customfilter. Agregamos variables de entorno para manejar url de adjuntos con o sin clave de encriptacion.
This commit is contained in:
		
							
								
								
									
										7
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								.env
									
									
									
									
									
								
							| @ -4,7 +4,6 @@ DATABASE_URL=ecto://postgres:1nf0rm3@127.0.0.1/dicomscp | |||||||
| CHECK_ORIGIN=https://estudios.nobissalud.com.ar | CHECK_ORIGIN=https://estudios.nobissalud.com.ar | ||||||
| PHX_PORT=4001 | PHX_PORT=4001 | ||||||
| PHX_HOST=127.0.0.1 | PHX_HOST=127.0.0.1 | ||||||
| #PHX_HOST=pacientes.sdlc.com.ar |  | ||||||
| ROOT_PATH=/api | ROOT_PATH=/api | ||||||
| SCHEME=https | SCHEME=https | ||||||
| #si es true debe estar escrito siempre en mayuscula (por que llega como string no como boolean) | #si es true debe estar escrito siempre en mayuscula (por que llega como string no como boolean) | ||||||
| @ -16,8 +15,10 @@ PYTHON_SCRIPT=/home/informemedico/impacs/cgi/soffice/generar_pdf_api.py | |||||||
| TEMPLATE_NAME=Con Membrete | TEMPLATE_NAME=Con Membrete | ||||||
| pr2_statusname=FINAL | pr2_statusname=FINAL | ||||||
| IDSITE=86 | IDSITE=86 | ||||||
| #ESCANEADOS=https://cedialcom.informemedico.com.ar:4443/cgi-bin/imageasjpeg.bf/imageasjpeg/ | ESCANEADOS=https://estudios.nobissalud.com.ar/ | ||||||
| ESCANEADOS=https://estudios.nobissalud.com.ar/# | # ESCANEADOS_CLAVE= | ||||||
|  | ADJUNTOS=https://estudios.nobissalud.com.ar/api/attachment/ | ||||||
|  | ADJUNTOS_CLAVE=1nf0rm3 | ||||||
| acceso_ed=IDSTUDY | acceso_ed=IDSTUDY | ||||||
| IDENTIFIERFIELD=IDSTUDY | IDENTIFIERFIELD=IDSTUDY | ||||||
|  |  | ||||||
|  | |||||||
| @ -3,14 +3,23 @@ defmodule Api.Studies do | |||||||
|   alias Api.Repo |   alias Api.Repo | ||||||
|  |  | ||||||
|   def studies_sql_query(filters) do |   def studies_sql_query(filters) do | ||||||
|  |     customfilter = | ||||||
|  |       case Map.get(filters, "customfilter") do | ||||||
|  |         nil -> dynamic([_], true) | ||||||
|  |         "" -> dynamic([_], true) | ||||||
|  |         expr -> | ||||||
|  |           case ExpressionParser.parse(expr) do | ||||||
|  |             {:ok, ast} -> ExpressionToEcto.to_dynamic(ast) | ||||||
|  |             {:error, _reason} -> raise "Error al parsear customfilter" | ||||||
|  |           end | ||||||
|  |       end | ||||||
|  |  | ||||||
|     page = filters["page"] || 1 |     page = filters["page"] || 1 | ||||||
|     size = filters["size"] || 24 |     size = filters["size"] || 24 | ||||||
|  |  | ||||||
|     filter = filters["filter"] || [] |     filter = filters["filter"] || [] | ||||||
|     sort = filters["sort"] || [%{"dir" => "desc", "field" => "studydate"}] |     sort = filters["sort"] || [%{"dir" => "desc", "field" => "studydate"}] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Construcción de condiciones de filtro dinámicas |     # Construcción de condiciones de filtro dinámicas | ||||||
|     filter_conditions = |     filter_conditions = | ||||||
|       Enum.reduce(filter, dynamic(true), fn f, acc -> |       Enum.reduce(filter, dynamic(true), fn f, acc -> | ||||||
| @ -23,7 +32,7 @@ defmodule Api.Studies do | |||||||
|               dynamic([s], ilike(s.modality, ^"%#{value}%")) |               dynamic([s], ilike(s.modality, ^"%#{value}%")) | ||||||
|  |  | ||||||
|             "idstudy" -> |             "idstudy" -> | ||||||
|               dynamic([s], s.idstudy == ^String.to_integer(value)) |               dynamic([s], like(fragment("CAST(? AS TEXT)", s.idstudy), ^"#{value}%")) | ||||||
|  |  | ||||||
|             "studydate" -> |             "studydate" -> | ||||||
|               dynamic([s], fragment("CAST(? AS DATE)::text LIKE ?", s.studydate, ^"%#{value}%")) |               dynamic([s], fragment("CAST(? AS DATE)::text LIKE ?", s.studydate, ^"%#{value}%")) | ||||||
| @ -82,35 +91,37 @@ defmodule Api.Studies do | |||||||
|         {direction, field} |         {direction, field} | ||||||
|       end) |       end) | ||||||
|  |  | ||||||
|  |     combined_filter = | ||||||
|  |       dynamic([q], ^filter_conditions and ^customfilter) | ||||||
|  |  | ||||||
|       query = |       query = | ||||||
|       from s in "study", |         from s in "study", | ||||||
|         join: p in "patient", |           join: p in "patient", | ||||||
|         on: p.idpatient == s.idpatient, |           on: p.idpatient == s.idpatient, | ||||||
|         left_join: sr in "studyreport", |           left_join: sr in "studyreport", | ||||||
|         on: sr.idstudy == s.idstudy, |           on: sr.idstudy == s.idstudy, | ||||||
|         left_join: st in "statuses", |           left_join: st in "statuses", | ||||||
|         on: st.idstatus == sr.idstudyreport, |           on: st.idstatus == sr.idstudyreport, | ||||||
|         where: ^filter_conditions, |           where: ^combined_filter, | ||||||
|         select: %{ |           select: %{ | ||||||
|           recordstotal: fragment("count(*) over()"), |             recordstotal: fragment("count(*) over()"), | ||||||
|           idstudy: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", s.idstudy), |             idstudy: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", s.idstudy), | ||||||
|           #idstudy: s.idstudy, |             #idstudy: s.idstudy, | ||||||
|           accessionnumber: s.accessionnumber, |             accessionnumber: s.accessionnumber, | ||||||
|           studydate: s.studydate, |             studydate: s.studydate, | ||||||
|           studytime: s.studytime, |             studytime: s.studytime, | ||||||
|           patientname: fragment("select replace(?, '^', ' ')", p.patientname), |             patientname: fragment("select replace(?, '^', ' ')", p.patientname), | ||||||
|           proceduredescription: s.studydescription, |             proceduredescription: s.studydescription, | ||||||
|           modality: s.modality, |             modality: s.modality, | ||||||
|           sitename: s.institutionname, |             sitename: s.institutionname, | ||||||
|           insurer: s.insurer, |             insurer: s.insurer, | ||||||
|           nrodocumento: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", p.patientid), |             nrodocumento: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", p.patientid), | ||||||
|           #nrodocumento: p.patientid, |             #nrodocumento: p.patientid, | ||||||
|           hasaudio: fragment("CASE WHEN ? IS NOT NULL THEN true ELSE false END", s.audiofile) |             hasaudio: fragment("CASE WHEN ? IS NOT NULL THEN true ELSE false END", s.audiofile) | ||||||
|         }, |           }, | ||||||
|         order_by: ^sort_conditions, |           order_by: ^sort_conditions, | ||||||
|         limit: ^size, |           limit: ^size, | ||||||
|         offset: ^((page - 1) * size) |           offset: ^((page - 1) * size) | ||||||
|  |  | ||||||
|  |  | ||||||
|     result = Repo.one( |     result = Repo.one( | ||||||
|  | |||||||
| @ -9,7 +9,6 @@ defmodule Api.Study do | |||||||
|  |  | ||||||
|     idsite = Envar.get("IDSITE") |> String.to_integer |     idsite = Envar.get("IDSITE") |> String.to_integer | ||||||
|     domain = Envar.get("CHECK_ORIGIN") |     domain = Envar.get("CHECK_ORIGIN") | ||||||
|     escaneados = Envar.get("ESCANEADOS") |  | ||||||
|  |  | ||||||
|     id_study = if Envar.get("IDENTIFIERFIELD") == "ACCESSIONNUMBER" do |     id_study = if Envar.get("IDENTIFIERFIELD") == "ACCESSIONNUMBER" do | ||||||
|       query = |       query = | ||||||
| @ -25,15 +24,6 @@ defmodule Api.Study do | |||||||
|  |  | ||||||
|     id_study = if is_integer(id_study), do: id_study, else: String.to_integer(id_study) |     id_study = if is_integer(id_study), do: id_study, else: String.to_integer(id_study) | ||||||
|  |  | ||||||
|  |  | ||||||
|     # accession_fragment = |  | ||||||
|     #   if Envar.get("IDENTIFIERFIELD") == "ACCESSIONNUMBER" do |  | ||||||
|     #     fragment("?::text", ^accession) |  | ||||||
|     #   else |  | ||||||
|     #     fragment("?::integer", ^id_study) |  | ||||||
|     #   end |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     Logger.info("id_study -------> #{inspect(id_study)}") |     Logger.info("id_study -------> #{inspect(id_study)}") | ||||||
|  |  | ||||||
|     # Las siguientes consultas obtienen la información completa |     # Las siguientes consultas obtienen la información completa | ||||||
| @ -70,56 +60,109 @@ defmodule Api.Study do | |||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|     # escaneados |     # escaneados | ||||||
|     subquery2 = subquery( |     escaneados_clave = Envar.get("ESCANEADOS_CLAVE") | ||||||
|       from s in "study", |     base_escaneados = Envar.get("ESCANEADOS") | ||||||
|         join: ss in "studyscans", on: ss.idstudy == s.idstudy, |  | ||||||
|         join: sc in "scanclasses", on: sc.idscanclass == ss.idscanclass, |     escaneados_expr = | ||||||
|         join: p in "patient", on: s.idpatient == p.idpatient, |       if escaneados_clave do | ||||||
|         where: s.idstudy == ^id_study, |         dynamic([_, ss, _, _], | ||||||
|         select: %{ |           fragment( | ||||||
|           idsite: type(^idsite, :integer), |             "concat(?::text, substring(encrypt(?::text::bytea, ?, 'aes')::text from 3))", | ||||||
|  |             ^base_escaneados, | ||||||
|  |             ss.idstudyscan, | ||||||
|  |             ^escaneados_clave | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |       else | ||||||
|  |         dynamic([_, ss, _, _], | ||||||
|  |           fragment( | ||||||
|  |             "concat(?::text, ?::text)", | ||||||
|  |             ^base_escaneados, | ||||||
|  |             ss.idstudyscan | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |     select_expr_es = | ||||||
|  |       dynamic([s, ss, sc, p], %{ | ||||||
|  |         idsite: type(^idsite, :integer), | ||||||
|           iddocument: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", ss.idstudyscan), |           iddocument: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", ss.idstudyscan), | ||||||
|           document_name: sc.scanclass, |           document_name: sc.scanclass, | ||||||
|           document_type: "url", |           document_type: "url", | ||||||
|           url: fragment( |           url: ^escaneados_expr, | ||||||
|             "concat(?::text, substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3))", |  | ||||||
|             ^escaneados, |  | ||||||
|             ss.idstudyscan |  | ||||||
|           ), |  | ||||||
|           patientname: p.patientname, |           patientname: p.patientname, | ||||||
|           proceduredescription: s.studydescription, |           proceduredescription: s.studydescription, | ||||||
|           studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate), |           studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate), | ||||||
|           studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime), |           studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime), | ||||||
|           accessionnumber: s.idstudy, |           accessionnumber: s.idstudy, | ||||||
|           patientid: p.patientid |           patientid: p.patientid | ||||||
|         } |       }) | ||||||
|  |  | ||||||
|  |     subquery2 = subquery( | ||||||
|  |       from s in "study", | ||||||
|  |         join: ss in "studyscans", on: ss.idstudy == s.idstudy, | ||||||
|  |         join: sc in "scanclasses", on: sc.idscanclass == ss.idscanclass, | ||||||
|  |         join: p in "patient", on: s.idpatient == p.idpatient, | ||||||
|  |         where: s.idstudy == ^id_study, | ||||||
|  |         select: ^select_expr_es | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     # adjuntos |     # adjuntos | ||||||
|     subquery3 = |     adjuntos_clave = Envar.get("ADJUNTOS_CLAVE") | ||||||
|  |     base_adjuntos = Envar.get("ADJUNTOS") | ||||||
|  |  | ||||||
|  |     adjuntos_expr = | ||||||
|  |       if adjuntos_clave do | ||||||
|  |         dynamic([_, sa, _], | ||||||
|  |           fragment( | ||||||
|  |             "concat(?::text, substring(encrypt(?::text::bytea, ?, 'aes')::text from 3))", | ||||||
|  |             ^base_adjuntos, | ||||||
|  |             sa.idstudyattachment, | ||||||
|  |             ^adjuntos_clave | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |       else | ||||||
|  |         dynamic([_, sa, _], | ||||||
|  |           fragment( | ||||||
|  |             "concat(?::text, ?::text)", | ||||||
|  |             ^base_adjuntos, | ||||||
|  |             sa.idstudyattachment | ||||||
|  |           ) | ||||||
|  |         ) | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |     select_expr_ad = | ||||||
|  |       dynamic([s, sa, p], %{ | ||||||
|  |         idsite: type(^idsite, :integer), | ||||||
|  |         iddocument: | ||||||
|  |           fragment( | ||||||
|  |             "substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", | ||||||
|  |             sa.idstudyattachment | ||||||
|  |           ), | ||||||
|  |         document_name: sa.name, | ||||||
|  |         document_type: | ||||||
|  |           fragment( | ||||||
|  |             "CASE WHEN ? NOT IN ('jpg', 'jpeg', 'png') THEN 'attachment' ELSE 'url' END", | ||||||
|  |             sa.format | ||||||
|  |           ), | ||||||
|  |         url: ^adjuntos_expr, | ||||||
|  |         patientname: p.patientname, | ||||||
|  |         proceduredescription: s.studydescription, | ||||||
|  |         studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate), | ||||||
|  |         studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime), | ||||||
|  |         accessionnumber: s.idstudy, | ||||||
|  |         patientid: "" | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |     subquery3 = subquery( | ||||||
|       from s in "study", |       from s in "study", | ||||||
|         join: sa in "studyattachments", on: sa.idstudy == s.idstudy, |         join: sa in "studyattachments", on: sa.idstudy == s.idstudy, | ||||||
|         join: p in "patient", on: p.idpatient == s.idpatient, |         join: p in "patient", on: p.idpatient == s.idpatient, | ||||||
|         where: s.idstudy == ^id_study, |         where: s.idstudy == ^id_study, | ||||||
|         select: %{ |         select: ^select_expr_ad | ||||||
|           idsite: type(^idsite, :integer), |     ) | ||||||
|           iddocument: fragment("substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3)", sa.idstudyattachment), |  | ||||||
|           document_name: sa.name, |  | ||||||
|           document_type: fragment("CASE WHEN ? NOT IN ('jpg', 'jpeg', 'png') THEN 'attachment' ELSE 'url' END", sa.format), |  | ||||||
|           url: fragment( |  | ||||||
|             "concat(?::text, ?::text, substring(encrypt(?::text::bytea, '1nf0rm3', 'aes')::text from 3))", |  | ||||||
|             ^domain, |  | ||||||
|             "/api/attachment/", |  | ||||||
|             sa.idstudyattachment |  | ||||||
|           ), |  | ||||||
|           patientname: p.patientname, |  | ||||||
|           proceduredescription: s.studydescription, |  | ||||||
|           studydate: fragment("TO_CHAR(?, 'YYYY-MM-DD')", s.studydate), |  | ||||||
|           studytime: fragment("TO_CHAR(?, 'HH24:MI:SS')", s.studytime), |  | ||||||
|           accessionnumber: s.idstudy, |  | ||||||
|           patientid: "" |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     # Si no encontró informes, ni adjuntos, ni nada |     # Si no encontró informes, ni adjuntos, ni nada | ||||||
|     # se traen sólo los datos básicos del estudio |     # se traen sólo los datos básicos del estudio | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ defmodule Api.Application do | |||||||
|     children = [ |     children = [ | ||||||
|       ApiWeb.Telemetry, |       ApiWeb.Telemetry, | ||||||
|       Api.Repo, |       Api.Repo, | ||||||
|         {Api.Autosender, 1000 * 62 * 4}, |       # {Api.Autosender, 1000 * 60}, | ||||||
|         # {DNSCluster, query: Application.get_env(:api, :dns_cluster_query) || :ignore}, |         # {DNSCluster, query: Application.get_env(:api, :dns_cluster_query) || :ignore}, | ||||||
|       {Phoenix.PubSub, name: Api.PubSub}, |       {Phoenix.PubSub, name: Api.PubSub}, | ||||||
|       # Start the Finch HTTP client for sending emails |       # Start the Finch HTTP client for sending emails | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ defmodule Api.Emailtosend do | |||||||
|     field :errormsg, :string |     field :errormsg, :string | ||||||
|     field :forcereprocess, :boolean |     field :forcereprocess, :boolean | ||||||
|     field :sentdatetime, :naive_datetime |     field :sentdatetime, :naive_datetime | ||||||
|  |     field :registered, :utc_datetime | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def changeset(emailtosend, attrs) do |   def changeset(emailtosend, attrs) do | ||||||
|  | |||||||
							
								
								
									
										124
									
								
								lib/api/parser/expression_parser.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								lib/api/parser/expression_parser.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | |||||||
|  | defmodule ExpressionParser do | ||||||
|  |   import NimbleParsec | ||||||
|  |  | ||||||
|  |   whitespace = ignore(optional(ascii_string([?\s, ?\t, ?\n, ?\r], min: 1))) | ||||||
|  |  | ||||||
|  |   identifier = | ||||||
|  |     ascii_string([?a..?z, ?A..?Z, ?_, ?0..?9], min: 1) | ||||||
|  |     |> label("identifier") | ||||||
|  |     |> unwrap_and_tag(:field) | ||||||
|  |  | ||||||
|  |   quoted_string = | ||||||
|  |     ignore(string("\"")) | ||||||
|  |     |> repeat(utf8_char(not: ?")) | ||||||
|  |     |> reduce({List, :to_string, []}) | ||||||
|  |     |> ignore(string("\"")) | ||||||
|  |  | ||||||
|  |   number = | ||||||
|  |     ascii_string([?0..?9], min: 1) | ||||||
|  |     |> map({String, :to_integer, []}) | ||||||
|  |  | ||||||
|  |   atom_value = | ||||||
|  |     ignore(string("'")) | ||||||
|  |     |> ascii_string([?a..?z, ?A..?Z], min: 1) | ||||||
|  |     |> ignore(string("'")) | ||||||
|  |     |> map({__MODULE__, :identity, []}) | ||||||
|  |  | ||||||
|  |   list_item = | ||||||
|  |     choice([ | ||||||
|  |       number, | ||||||
|  |       atom_value, | ||||||
|  |       quoted_string |> unwrap_and_tag(:string) | ||||||
|  |     ]) | ||||||
|  |  | ||||||
|  |   atom_list = | ||||||
|  |     ignore(string("[")) | ||||||
|  |     |> optional(whitespace) | ||||||
|  |     |> repeat( | ||||||
|  |       list_item | ||||||
|  |       |> optional(ignore(string(","))) | ||||||
|  |       |> optional(whitespace) | ||||||
|  |     ) | ||||||
|  |     |> ignore(string("]")) | ||||||
|  |     |> tag(:list) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   operator = | ||||||
|  |     choice([ | ||||||
|  |       string("ilike") |> replace(:ilike), | ||||||
|  |       string(">=") |> replace(:>=), | ||||||
|  |       string("<=") |> replace(:<=), | ||||||
|  |       string("!=") |> replace(:!=), | ||||||
|  |       string(">") |> replace(:>), | ||||||
|  |       string("<") |> replace(:<), | ||||||
|  |       string("=") |> replace(:==), | ||||||
|  |       string("in") |> replace(:in) | ||||||
|  |     ]) | ||||||
|  |  | ||||||
|  |   value = | ||||||
|  |     choice([ | ||||||
|  |       quoted_string |> unwrap_and_tag(:string), | ||||||
|  |       number |> unwrap_and_tag(:number), | ||||||
|  |       atom_list | ||||||
|  |     ]) | ||||||
|  |  | ||||||
|  |   comparison = | ||||||
|  |     identifier | ||||||
|  |     |> ignore(whitespace) | ||||||
|  |     |> concat(operator) | ||||||
|  |     |> ignore(whitespace) | ||||||
|  |     |> concat(value) | ||||||
|  |     |> tag(:comparison) | ||||||
|  |  | ||||||
|  |   defcombinatorp :expr, parsec(:logic_expr) | ||||||
|  |  | ||||||
|  |   paren_expr = | ||||||
|  |     ignore(string("(")) | ||||||
|  |     |> ignore(whitespace) | ||||||
|  |     |> parsec(:expr) | ||||||
|  |     |> ignore(whitespace) | ||||||
|  |     |> ignore(string(")")) | ||||||
|  |  | ||||||
|  |   logic_term = | ||||||
|  |     choice([ | ||||||
|  |       paren_expr, | ||||||
|  |       comparison | ||||||
|  |     ]) | ||||||
|  |  | ||||||
|  |   logical_op = | ||||||
|  |     ignore(whitespace) | ||||||
|  |     |> choice([ | ||||||
|  |       string("and") |> replace(:and), | ||||||
|  |       string("or") |> replace(:or) | ||||||
|  |     ]) | ||||||
|  |     |> ignore(whitespace) | ||||||
|  |  | ||||||
|  |   logic_expr = | ||||||
|  |     logic_term | ||||||
|  |     |> repeat( | ||||||
|  |       logical_op | ||||||
|  |       |> concat(logic_term) | ||||||
|  |     ) | ||||||
|  |     |> reduce({__MODULE__, :reduce_logic, []}) | ||||||
|  |  | ||||||
|  |   defparsec :logic_expr, logic_expr | ||||||
|  |  | ||||||
|  |   def parse(input) do | ||||||
|  |     case logic_expr(input) do | ||||||
|  |       {:ok, result, "", _, _, _} -> {:ok, result} | ||||||
|  |       {:ok, _, rest, _, _, _} -> {:error, "Unexpected remaining input: #{inspect(rest)}"} | ||||||
|  |       {:error, reason, _, _, _, _} -> {:error, reason} | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def reduce_logic([head | tail]) do | ||||||
|  |     Enum.chunk_every(tail, 2) | ||||||
|  |     |> Enum.reduce(head, fn [op, right], acc -> | ||||||
|  |       {op, acc, right} | ||||||
|  |     end) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def identity(value), do: value | ||||||
|  |  | ||||||
|  | end | ||||||
							
								
								
									
										59
									
								
								lib/api/parser/expression_to_ecto.ex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								lib/api/parser/expression_to_ecto.ex
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | defmodule ExpressionToEcto do | ||||||
|  |   import Ecto.Query | ||||||
|  |  | ||||||
|  |   def to_dynamic([]), do: true | ||||||
|  |  | ||||||
|  |   def to_dynamic([single]), do: build_dynamic(single) | ||||||
|  |  | ||||||
|  |   def to_dynamic(ast), do: build_dynamic(ast) | ||||||
|  |  | ||||||
|  |   # Operadores lógicos | ||||||
|  |   defp build_dynamic({:or, left, right}) do | ||||||
|  |     dynamic([q], ^build_dynamic(left) or ^build_dynamic(right)) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   defp build_dynamic({:and, left, right}) do | ||||||
|  |     dynamic([q], ^build_dynamic(left) and ^build_dynamic(right)) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   # Comparaciones con número | ||||||
|  |   defp build_dynamic({:comparison, [{:field, field}, op, {:number, val}]}) do | ||||||
|  |     field_atom = String.to_atom(field) | ||||||
|  |  | ||||||
|  |     case op do | ||||||
|  |       :== -> dynamic([q], field(q, ^field_atom) == ^val) | ||||||
|  |       :!= -> dynamic([q], field(q, ^field_atom) != ^val) | ||||||
|  |       :>  -> dynamic([q], field(q, ^field_atom) > ^val) | ||||||
|  |       :<  -> dynamic([q], field(q, ^field_atom) < ^val) | ||||||
|  |       :>= -> dynamic([q], field(q, ^field_atom) >= ^val) | ||||||
|  |       :<= -> dynamic([q], field(q, ^field_atom) <= ^val) | ||||||
|  |       other -> raise "Operador no soportado: #{inspect(other)}" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   # Comparaciones con string | ||||||
|  |   defp build_dynamic({:comparison, [{:field, field}, op, {:string, val}]}) when op in [:==, :!=] do | ||||||
|  |     field_atom = String.to_atom(field) | ||||||
|  |       case op do | ||||||
|  |         :== -> dynamic([q], field(q, ^field_atom) == ^val) | ||||||
|  |         :!= -> dynamic([q], field(q, ^field_atom) != ^val) | ||||||
|  |       end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   #  ILIKE | ||||||
|  |   defp build_dynamic({:comparison, [{:field, field}, :ilike, {:string, val}]}) do | ||||||
|  |     field_atom = String.to_atom(field) | ||||||
|  |     pattern = "#{val}" | ||||||
|  |     dynamic([q], ilike(field(q, ^field_atom), ^pattern)) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   # IN | ||||||
|  |   defp build_dynamic({:comparison, [{:field, field}, :in, {:list, vals}]}) do | ||||||
|  |     field_atom = String.to_atom(field) | ||||||
|  |     dynamic([q], field(q, ^field_atom) in ^vals) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   defp build_dynamic(other) do | ||||||
|  |     raise "AST no soportado o mal formado: #{inspect(other)}" | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										3
									
								
								mix.exs
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								mix.exs
									
									
									
									
									
								
							| @ -64,7 +64,8 @@ defmodule Api.MixProject do | |||||||
|       {:ex_heroicons, "~> 2.0.0"}, |       {:ex_heroicons, "~> 2.0.0"}, | ||||||
|       {:castore, "~> 1.0.10"}, |       {:castore, "~> 1.0.10"}, | ||||||
|       {:dotenv, "~> 3.1"}, |       {:dotenv, "~> 3.1"}, | ||||||
|       {:httpoison, "~> 2.2"} |       {:httpoison, "~> 2.2"}, | ||||||
|  |       {:nimble_parsec, "~> 1.4"} | ||||||
|     ] |     ] | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user