defmodule Minirate do @moduledoc """ A simple distributed rate limiter using Mnesia. ## Usage case Minirate.check_limit("login", "user@example.com", 5) do {:allow, count} -> :ok {:block, :limit_exceeded} -> {:error, :rate_limited} end ## Configuration config :minirate, mnesia_table: :rate_limiter, expiry_ms: 60_000, cleanup_period_ms: 300_000 """ use Application @doc false def start(_type, _args) do children = [ %{ id: Minirate.Worker, start: {Minirate.Worker, :start_link, [mnesia_table(), expiry_ms(), cleanup_period_ms()]} } ] Supervisor.start_link(children, strategy: :one_for_one) end @doc """ Check if an action is within the rate limit. Returns `{:allow, count}` if allowed or `{:block, :limit_exceeded}` if exceeded. """ def check_limit(action, id, limit) do Minirate.Counter.check_limit(mnesia_table(), {action, id, limit, now()}) end @doc """ Check rate limit with custom increment. """ def check_limit(action, id, limit, increment) do Minirate.Counter.check_limit(mnesia_table(), {action, id, limit, now()}, increment) end defp expiry_ms, do: get_config(:expiry_ms) defp mnesia_table, do: get_config(:mnesia_table) defp cleanup_period_ms, do: get_config(:cleanup_period_ms) defp get_config(key) do case Application.get_env(:minirate, key) do nil -> Kernel.raise( ArgumentError, "the configuration parameter #{Kernel.inspect(key)} is not set" ) value -> value end end defp now do DateTime.utc_now() |> DateTime.to_unix(:millisecond) end end