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

View file

@ -7,6 +7,7 @@ defmodule AprsmeWeb.TimeHelpers do
@doc """
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(%NaiveDateTime{} = naive_datetime) do
@ -42,6 +43,7 @@ defmodule AprsmeWeb.TimeHelpers do
Handles ISO 8601 strings, Unix timestamps in milliseconds, and existing
DateTime or NaiveDateTime structs.
"""
@spec to_datetime(String.t() | integer() | DateTime.t() | NaiveDateTime.t() | any()) :: DateTime.t() | nil
def to_datetime(ts) do
cond do
is_binary(ts) ->

View file

@ -10,6 +10,8 @@ defmodule AprsmeWeb.UserAuth do
# Make the remember me cookie valid for 60 days.
# If you want bump or reduce this value, also change
# the token expiry itself in UserToken.
alias Phoenix.LiveView.Socket
@max_age 60 * 60 * 24 * 60
@remember_me_cookie "_aprs_web_user_remember_me"
@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
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
token = Accounts.generate_user_session_token(user)
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.
"""
@spec log_out_user(Plug.Conn.t()) :: Plug.Conn.t()
def log_out_user(conn) do
user_token = get_session(conn, :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
and remember me token.
"""
@spec fetch_current_user(Plug.Conn.t(), any()) :: Plug.Conn.t()
def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn)
user = user_token && Accounts.get_user_by_session_token(user_token)
@ -144,6 +149,7 @@ defmodule AprsmeWeb.UserAuth do
live "/profile", ProfileLive, :index
end
"""
@spec on_mount(atom(), map(), map(), Socket.t()) :: {:cont | :halt, Socket.t()}
def on_mount(:mount_current_user, _params, session, socket) do
{:cont, mount_current_user(session, socket)}
end
@ -189,6 +195,7 @@ defmodule AprsmeWeb.UserAuth do
@doc """
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
case conn.assigns[:current_user] do
nil ->
@ -207,6 +214,7 @@ defmodule AprsmeWeb.UserAuth do
If you want to enforce the user email is confirmed before
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
case conn.assigns[:current_user] do
nil ->

View file

@ -10,6 +10,7 @@ defmodule AprsmeWeb.WeatherUnits do
Determines the unit system based on locale.
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
case locale do
# Default to imperial for English
@ -32,6 +33,7 @@ defmodule AprsmeWeb.WeatherUnits do
Converts temperature from Fahrenheit to Celsius if needed.
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
case unit_system(locale) do
:imperial -> {temp, "°F"}
@ -45,6 +47,7 @@ defmodule AprsmeWeb.WeatherUnits do
Converts wind speed from mph to km/h if needed.
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
case unit_system(locale) do
:imperial -> {speed, "mph"}
@ -58,6 +61,7 @@ defmodule AprsmeWeb.WeatherUnits do
Converts rain from inches to mm if needed.
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
case unit_system(locale) do
:imperial -> {rain, "in"}
@ -70,6 +74,7 @@ defmodule AprsmeWeb.WeatherUnits do
@doc """
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
{pressure, "hPa"}
end
@ -79,6 +84,12 @@ defmodule AprsmeWeb.WeatherUnits do
@doc """
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
case unit_system(locale) do
:imperial ->