location_store.ex 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. defmodule Vaccins.LocationStore do
  2. defmodule Location do
  3. alias __MODULE__, as: Location
  4. @limit 60 * 60 * 24
  5. defstruct [
  6. :id,
  7. :name,
  8. :availability_query,
  9. :booking_page,
  10. :provider,
  11. availability_query_params: []
  12. ]
  13. def set_id(l = %Location{name: name}), do: %{l | id: name |> String.to_atom()}
  14. def build_query(l = %Location{availability_query_params: params, provider: provider})
  15. when params != [],
  16. do: %{l | availability_query: params |> provider.new}
  17. def query_availability(
  18. l = %Location{id: id, availability_query: q, booking_page: bp, provider: provider}
  19. ) do
  20. with url <- q |> provider.to_url() |> URI.to_string(),
  21. {:ok, result} <- url |> Vaccins.Scraper.get_json() do
  22. case result |> provider.analyze_result() do
  23. {:ok, slots} ->
  24. case slots
  25. |> Enum.map(&DateTime.truncate(&1, :second))
  26. |> Enum.group_by(&(&1 |> DateTime.diff(DateTime.utc_now()) |> abs < @limit)) do
  27. grouped = %{true: before_limit} ->
  28. {:ok, before_limit |> sort, grouped |> Map.get(false, []) |> sort}
  29. %{false: after_limit} ->
  30. {:ok, after_limit |> sort}
  31. end
  32. error = {:error, reason} when reason in [:no_availability] ->
  33. error
  34. end
  35. end
  36. end
  37. defp sort(list) when is_list(list), do: list |> Enum.sort_by(& &1, {:asc, DateTime})
  38. end
  39. defmodule LocationRaw do
  40. use Ecto.Schema
  41. import Ecto.Changeset
  42. @primary_key {:id, :id, autogenerate: false}
  43. embedded_schema do
  44. field(:name, :string)
  45. field(:booking_page, :string)
  46. field(:raw_query, :string)
  47. end
  48. @doc false
  49. def changeset(location \\ %__MODULE__{}, attrs),
  50. do:
  51. location
  52. |> cast(attrs, [:name, :booking_page, :raw_query])
  53. |> validate_required([:name, :booking_page, :raw_query])
  54. def to_query_params(%__MODULE__{raw_query: raw}) do
  55. raw
  56. |> URI.parse()
  57. |> Map.get(:query)
  58. |> URI.decode_query()
  59. |> Map.new(fn {k, v} -> {k |> String.to_atom(), v} end)
  60. |> Map.take([:agenda_ids, :limit, :practice_ids, :visit_motive_ids])
  61. end
  62. end
  63. require Ex2ms
  64. import Ecto.Changeset
  65. alias Vaccins.Queries.Doctolib
  66. @name Vaccins.LocationStore
  67. def get_locations() do
  68. select_all =
  69. Ex2ms.fun do
  70. x -> x
  71. end
  72. :dets.select(@name, select_all) |> Enum.map(fn {_k, v} -> v end)
  73. end
  74. def insert_all(locations) do
  75. locations |> Enum.each(&:dets.insert(@name, {&1.id, &1}))
  76. end
  77. def build_location(l = %Location{}), do: l |> Location.set_id() |> Location.build_query()
  78. def add_location(params) do
  79. cs = params |> LocationRaw.changeset()
  80. with {:ok, raw_location} <- cs |> apply_action(:insert),
  81. processed <-
  82. %Location{
  83. name: raw_location.name,
  84. booking_page: raw_location.booking_page,
  85. availability_query_params: raw_location |> LocationRaw.to_query_params(),
  86. provider: Doctolib
  87. }
  88. |> build_location,
  89. true <- :dets.insert_new(@name, {processed.id, processed}) do
  90. :ok
  91. else
  92. false -> cs |> add_error(:name, "error on insertion", []) |> apply_action(:insert)
  93. e = {:error, _} -> e
  94. end
  95. end
  96. end