fix(errors): LIKE wildcard escaping, memory-efficient counts, context tests
- Escape LIKE wildcards (% and _) in search queries using fragment ESCAPE to prevent accidental pattern expansion and potential DoS vectors - Use Repo.aggregate for occurrence_count instead of loading all occurrences into memory in get_error/1 - Use Repo.aggregate(:min, :inserted_at) for first_occurrence_at instead of fragile List.last/1 that depends on query ordering - Add MusicLibrary.ErrorsTest with 15 tests covering list_errors/1 (default, status/muted/search filters, wildcard escaping, pagination, empty results) and get_error/1 (with/without occurrences, not_found)
This commit is contained in:
@@ -62,8 +62,13 @@ defmodule MusicLibrary.Errors do
|
||||
)
|
||||
|> Repo.all()
|
||||
|
||||
occurrence_count = length(occurrences)
|
||||
first_occurrence_at = get_first_occurrence_at(occurrences)
|
||||
occurrence_count =
|
||||
from(o in Occurrence, where: o.error_id == ^id)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
first_occurrence_at =
|
||||
from(o in Occurrence, where: o.error_id == ^id)
|
||||
|> Repo.aggregate(:min, :inserted_at)
|
||||
|
||||
result =
|
||||
%{error | occurrences: occurrences}
|
||||
@@ -93,9 +98,15 @@ defmodule MusicLibrary.Errors do
|
||||
defp maybe_filter_search(query, ""), do: query
|
||||
|
||||
defp maybe_filter_search(query, search) do
|
||||
where(query, [e], like(e.reason, ^"%#{search}%"))
|
||||
escaped = escape_like_wildcards(search)
|
||||
where(query, [e], fragment("? LIKE ? ESCAPE '\\'", e.reason, ^"%#{escaped}%"))
|
||||
end
|
||||
|
||||
defp get_first_occurrence_at([]), do: nil
|
||||
defp get_first_occurrence_at(occurrences), do: List.last(occurrences).inserted_at
|
||||
@doc false
|
||||
def escape_like_wildcards(search) when is_binary(search) do
|
||||
search
|
||||
|> String.replace("\\", "\\\\")
|
||||
|> String.replace("%", "\\%")
|
||||
|> String.replace("_", "\\_")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user