prop/lib/microwaveprop/beacon_monitors/beacon_monitor.ex
Graham McIntire fb49eb016d
feat(monitors): schema migration + remove user self-service creation
- Add hardware/config fields migration to beacon_monitors table
- Update BeaconMonitor schema with provision/config changesets
- Add context functions: create_hardware, update_config, list_all_monitors
- Remove user-facing monitor creation (browser POST + API POST)
- Update settings page: show assigned monitors table with hardware info
- Update profile page: show assigned monitors, remove register links
- Fix all tests to match new API
2026-07-21 18:28:51 -05:00

111 lines
3.5 KiB
Elixir

defmodule Microwaveprop.BeaconMonitors.BeaconMonitor do
@moduledoc """
A beacon monitor is a physical SDR-based hardware unit assigned to a
user. The unit runs the `propmonitor` client which reports beacon
reception measurements back to Microwaveprop.
Each monitor has a unique random token the client uses to authenticate
its measurement uploads.
"""
use Ecto.Schema
import Ecto.Changeset
alias Microwaveprop.Accounts.User
alias Microwaveprop.Beacons.Beacon
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "beacon_monitors" do
# Identity / label
field :name, :string
field :token, :string
field :last_seen_at, :utc_datetime
# Hardware identity
field :hardware_type, :string
field :hardware_id, :string
field :firmware_version, :string
# Antenna / installation
field :antenna_type, :string
field :antenna_gain_dbi, :float
field :lat, :float
field :lon, :float
# Active configuration (admin + assigned user can change)
field :config_frequency_hz, :integer
field :config_integration_s, :integer
field :config_mode, :string
# Relationships
belongs_to :user, User
belongs_to :beacon, Beacon
belongs_to :assigned_by, User
timestamps(type: :utc_datetime)
end
@type t :: %__MODULE__{}
@doc """
General changeset — allows basic fields. Context-level functions
enforce which caller-role can set which fields.
"""
@spec changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
def changeset(monitor, attrs) do
monitor
|> cast(attrs, [:name])
|> validate_required([:name])
|> validate_length(:name, min: 1, max: 100)
end
@doc """
Provisioning changeset — used by admins to register new hardware.
Casts all hardware-identity, antenna, and assignment fields.
"""
@spec provision_changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
def provision_changeset(monitor, attrs) do
monitor
|> cast(attrs, [
:name,
:hardware_type,
:hardware_id,
:firmware_version,
:antenna_type,
:antenna_gain_dbi,
:lat,
:lon,
:user_id,
:assigned_by_id
])
|> validate_required([:name])
|> validate_length(:name, min: 1, max: 100)
|> validate_length(:hardware_type, max: 100)
|> validate_length(:hardware_id, max: 255)
|> validate_length(:firmware_version, max: 50)
|> validate_length(:antenna_type, max: 100)
|> validate_number(:antenna_gain_dbi, greater_than_or_equal_to: -20, less_than_or_equal_to: 60)
|> validate_number(:lat, greater_than_or_equal_to: -90, less_than_or_equal_to: 90)
|> validate_number(:lon, greater_than_or_equal_to: -180, less_than_or_equal_to: 180)
|> foreign_key_constraint(:user_id)
|> foreign_key_constraint(:assigned_by_id)
end
@doc """
Configuration changeset — used by admins and assigned users to update
what the monitor is listening for.
"""
@spec config_changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
def config_changeset(monitor, attrs) do
monitor
|> cast(attrs, [:name, :beacon_id, :config_frequency_hz, :config_integration_s, :config_mode])
|> validate_required([:name])
|> validate_length(:name, min: 1, max: 100)
|> validate_length(:config_mode, max: 50)
|> validate_number(:config_frequency_hz, greater_than: 0)
|> validate_number(:config_integration_s, greater_than_or_equal_to: 5, less_than_or_equal_to: 3600)
|> foreign_key_constraint(:beacon_id)
end
end