Files
cobblemon-ui/lib/cobblemon_ui/poke_api.ex
Alex Mickelson 0fbb03f7de
All checks were successful
Build and Deploy / Build & Push Image (push) Successful in 33s
updates
2026-03-25 20:45:57 -06:00

90 lines
2.4 KiB
Elixir

defmodule CobblemonUi.PokeApi do
@moduledoc """
Fetches and caches Pokémon type data from PokeAPI.
Uses an ETS table for fast lookups. Data is fetched once per species
and cached for the lifetime of the process.
"""
use GenServer
require Logger
@table :poke_api_types_cache
# ---------------------------------------------------------------------------
# Client API
# ---------------------------------------------------------------------------
def start_link(_opts) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@doc """
Returns a list of type names (lowercase strings) for the given species.
Example: `get_types("bulbasaur")` → `["grass", "poison"]`
"""
@spec get_types(String.t()) :: [String.t()]
def get_types(species) when is_binary(species) do
key = String.downcase(species)
case :ets.lookup(@table, key) do
[{^key, types}] ->
types
[] ->
GenServer.call(__MODULE__, {:fetch_types, key}, 15_000)
end
end
# ---------------------------------------------------------------------------
# Server callbacks
# ---------------------------------------------------------------------------
@impl true
def init(_opts) do
:ets.new(@table, [:named_table, :set, :public, read_concurrency: true])
{:ok, %{}}
end
@impl true
def handle_call({:fetch_types, species}, _from, state) do
result =
case :ets.lookup(@table, species) do
[{^species, types}] ->
types
[] ->
types = fetch_types(species)
:ets.insert(@table, {species, types})
types
end
{:reply, result, state}
end
# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------
defp fetch_types(species) do
url = "https://pokeapi.co/api/v2/pokemon/#{species}"
case Req.get(url) do
{:ok, %Req.Response{status: 200, body: %{"types" => types}}} ->
types
|> Enum.sort_by(fn t -> t["slot"] end)
|> Enum.map(fn t -> get_in(t, ["type", "name"]) end)
|> Enum.reject(&is_nil/1)
{:ok, %Req.Response{status: status}} ->
Logger.warning("[PokeApi] Type lookup failed for #{species}: HTTP #{status}")
[]
{:error, reason} ->
Logger.warning("[PokeApi] Type lookup failed for #{species}: #{inspect(reason)}")
[]
end
end
end