Add missing type specifications to improve type safety

- Add @spec annotations to all public functions in time_helpers.ex
- Add @spec annotations to all public functions in weather_units.ex
- Add @spec annotations to authentication functions in user_auth.ex
- Fix User struct type reference (use %Aprsme.Accounts.User{} instead of t())
- All dialyzer checks now pass successfully

These type specifications improve code documentation and enable better
compile-time type checking with dialyzer.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-07-30 13:40:14 -05:00
parent a0258c0ff0
commit c8ae4aecaf
No known key found for this signature in database
4 changed files with 21 additions and 3 deletions

View file

@ -56,8 +56,6 @@ defmodule Aprsme.EncodingUtils do
def to_float(value) when is_integer(value) do def to_float(value) when is_integer(value) do
if value >= -9.0e15 and value <= 9.0e15 do if value >= -9.0e15 and value <= 9.0e15 do
value * 1.0 value * 1.0
else
nil
end end
end end
@ -77,7 +75,6 @@ defmodule Aprsme.EncodingUtils do
def to_float(_), do: nil def to_float(_), do: nil
@doc """ @doc """
Converts various types to Decimal for database storage. Converts various types to Decimal for database storage.

View file

@ -7,6 +7,7 @@ defmodule AprsmeWeb.TimeHelpers do
@doc """ @doc """
Returns a human-readable string for how long ago the given DateTime was. Returns a human-readable string for how long ago the given DateTime was.
""" """
@spec time_ago_in_words(nil | NaiveDateTime.t() | DateTime.t()) :: String.t()
def time_ago_in_words(nil), do: gettext("unknown time") <> " " <> gettext("ago") def time_ago_in_words(nil), do: gettext("unknown time") <> " " <> gettext("ago")
def time_ago_in_words(%NaiveDateTime{} = naive_datetime) do def time_ago_in_words(%NaiveDateTime{} = naive_datetime) do
@ -42,6 +43,7 @@ defmodule AprsmeWeb.TimeHelpers do
Handles ISO 8601 strings, Unix timestamps in milliseconds, and existing Handles ISO 8601 strings, Unix timestamps in milliseconds, and existing
DateTime or NaiveDateTime structs. DateTime or NaiveDateTime structs.
""" """
@spec to_datetime(String.t() | integer() | DateTime.t() | NaiveDateTime.t() | any()) :: DateTime.t() | nil
def to_datetime(ts) do def to_datetime(ts) do
cond do cond do
is_binary(ts) -> is_binary(ts) ->

View file

@ -10,6 +10,8 @@ defmodule AprsmeWeb.UserAuth do
# Make the remember me cookie valid for 60 days. # Make the remember me cookie valid for 60 days.
# If you want bump or reduce this value, also change # If you want bump or reduce this value, also change
# the token expiry itself in UserToken. # the token expiry itself in UserToken.
alias Phoenix.LiveView.Socket
@max_age 60 * 60 * 24 * 60 @max_age 60 * 60 * 24 * 60
@remember_me_cookie "_aprs_web_user_remember_me" @remember_me_cookie "_aprs_web_user_remember_me"
@remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"] @remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
@ -26,6 +28,7 @@ defmodule AprsmeWeb.UserAuth do
disconnected on log out. The line can be safely removed disconnected on log out. The line can be safely removed
if you are not using LiveView. if you are not using LiveView.
""" """
@spec log_in_user(Plug.Conn.t(), %Aprsme.Accounts.User{}, map()) :: Plug.Conn.t()
def log_in_user(conn, user, params \\ %{}) do def log_in_user(conn, user, params \\ %{}) do
token = Accounts.generate_user_session_token(user) token = Accounts.generate_user_session_token(user)
user_return_to = get_session(conn, :user_return_to) user_return_to = get_session(conn, :user_return_to)
@ -69,6 +72,7 @@ defmodule AprsmeWeb.UserAuth do
It clears all session data for safety. See renew_session. It clears all session data for safety. See renew_session.
""" """
@spec log_out_user(Plug.Conn.t()) :: Plug.Conn.t()
def log_out_user(conn) do def log_out_user(conn) do
user_token = get_session(conn, :user_token) user_token = get_session(conn, :user_token)
user_token && Accounts.delete_user_session_token(user_token) user_token && Accounts.delete_user_session_token(user_token)
@ -87,6 +91,7 @@ defmodule AprsmeWeb.UserAuth do
Authenticates the user by looking into the session Authenticates the user by looking into the session
and remember me token. and remember me token.
""" """
@spec fetch_current_user(Plug.Conn.t(), any()) :: Plug.Conn.t()
def fetch_current_user(conn, _opts) do def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn) {user_token, conn} = ensure_user_token(conn)
user = user_token && Accounts.get_user_by_session_token(user_token) user = user_token && Accounts.get_user_by_session_token(user_token)
@ -144,6 +149,7 @@ defmodule AprsmeWeb.UserAuth do
live "/profile", ProfileLive, :index live "/profile", ProfileLive, :index
end end
""" """
@spec on_mount(atom(), map(), map(), Socket.t()) :: {:cont | :halt, Socket.t()}
def on_mount(:mount_current_user, _params, session, socket) do def on_mount(:mount_current_user, _params, session, socket) do
{:cont, mount_current_user(session, socket)} {:cont, mount_current_user(session, socket)}
end end
@ -189,6 +195,7 @@ defmodule AprsmeWeb.UserAuth do
@doc """ @doc """
Used for routes that require the user to not be authenticated. Used for routes that require the user to not be authenticated.
""" """
@spec redirect_if_user_is_authenticated(Plug.Conn.t(), any()) :: Plug.Conn.t()
def redirect_if_user_is_authenticated(conn, _opts) do def redirect_if_user_is_authenticated(conn, _opts) do
case conn.assigns[:current_user] do case conn.assigns[:current_user] do
nil -> nil ->
@ -207,6 +214,7 @@ defmodule AprsmeWeb.UserAuth do
If you want to enforce the user email is confirmed before If you want to enforce the user email is confirmed before
they use the application at all, here would be a good place. they use the application at all, here would be a good place.
""" """
@spec require_authenticated_user(Plug.Conn.t(), any()) :: Plug.Conn.t()
def require_authenticated_user(conn, _opts) do def require_authenticated_user(conn, _opts) do
case conn.assigns[:current_user] do case conn.assigns[:current_user] do
nil -> nil ->

View file

@ -10,6 +10,7 @@ defmodule AprsmeWeb.WeatherUnits do
Determines the unit system based on locale. Determines the unit system based on locale.
Returns :imperial for US, :metric for most other countries. Returns :imperial for US, :metric for most other countries.
""" """
@spec unit_system(String.t() | nil | any()) :: :imperial | :metric
def unit_system(locale) when is_binary(locale) do def unit_system(locale) when is_binary(locale) do
case locale do case locale do
# Default to imperial for English # Default to imperial for English
@ -32,6 +33,7 @@ defmodule AprsmeWeb.WeatherUnits do
Converts temperature from Fahrenheit to Celsius if needed. Converts temperature from Fahrenheit to Celsius if needed.
Returns the temperature in the appropriate unit for the locale. Returns the temperature in the appropriate unit for the locale.
""" """
@spec format_temperature(number() | any(), String.t()) :: {number() | any(), String.t()}
def format_temperature(temp, locale) when is_number(temp) do def format_temperature(temp, locale) when is_number(temp) do
case unit_system(locale) do case unit_system(locale) do
:imperial -> {temp, "°F"} :imperial -> {temp, "°F"}
@ -45,6 +47,7 @@ defmodule AprsmeWeb.WeatherUnits do
Converts wind speed from mph to km/h if needed. Converts wind speed from mph to km/h if needed.
Returns the wind speed in the appropriate unit for the locale. Returns the wind speed in the appropriate unit for the locale.
""" """
@spec format_wind_speed(number() | any(), String.t()) :: {number() | any(), String.t()}
def format_wind_speed(speed, locale) when is_number(speed) do def format_wind_speed(speed, locale) when is_number(speed) do
case unit_system(locale) do case unit_system(locale) do
:imperial -> {speed, "mph"} :imperial -> {speed, "mph"}
@ -58,6 +61,7 @@ defmodule AprsmeWeb.WeatherUnits do
Converts rain from inches to mm if needed. Converts rain from inches to mm if needed.
Returns the rain amount in the appropriate unit for the locale. Returns the rain amount in the appropriate unit for the locale.
""" """
@spec format_rain(number() | any(), String.t()) :: {number() | any(), String.t()}
def format_rain(rain, locale) when is_number(rain) do def format_rain(rain, locale) when is_number(rain) do
case unit_system(locale) do case unit_system(locale) do
:imperial -> {rain, "in"} :imperial -> {rain, "in"}
@ -70,6 +74,7 @@ defmodule AprsmeWeb.WeatherUnits do
@doc """ @doc """
Formats pressure (keeps hPa for all locales as it's standard). Formats pressure (keeps hPa for all locales as it's standard).
""" """
@spec format_pressure(number() | any(), any()) :: {number() | any(), String.t()}
def format_pressure(pressure, _locale) when is_number(pressure) do def format_pressure(pressure, _locale) when is_number(pressure) do
{pressure, "hPa"} {pressure, "hPa"}
end end
@ -79,6 +84,12 @@ defmodule AprsmeWeb.WeatherUnits do
@doc """ @doc """
Gets the appropriate unit labels for the locale. Gets the appropriate unit labels for the locale.
""" """
@spec unit_labels(String.t()) :: %{
temperature: String.t(),
wind_speed: String.t(),
rain: String.t(),
pressure: String.t()
}
def unit_labels(locale) do def unit_labels(locale) do
case unit_system(locale) do case unit_system(locale) do
:imperial -> :imperial ->