Show beacon submitter callsign, add donate link, prefer 8-char grids

- Add belongs_to :user on Beacon schema, preload in get_beacon!/1
- Show "Submitted by {callsign}" on beacon detail page
- Add NTMS donation link on the about page
- Change grid preference text to 8 characters (e.g. EM12kp37)
This commit is contained in:
Graham McIntire 2026-04-11 18:07:56 -05:00
parent 1174ecd9e5
commit c3becdac9f
8 changed files with 53 additions and 17 deletions

View file

@ -47,7 +47,7 @@ defmodule Microwaveprop.Beacons do
end
@doc "Gets a single beacon. Raises if not found."
def get_beacon!(id), do: Repo.get!(Beacon, id)
def get_beacon!(id), do: Beacon |> Repo.get!(id) |> Repo.preload(:user)
@doc """
Creates a beacon. When a user is provided they are recorded as the

View file

@ -152,7 +152,8 @@ defmodule Microwaveprop.Beacons.Beacon do
field :bearing, :string, default: "omni"
field :beamwidth_deg, :float
field :notes, :string
field :user_id, :binary_id
belongs_to :user, Microwaveprop.Accounts.User
timestamps(type: :utc_datetime)
end

View file

@ -363,10 +363,10 @@ defmodule Microwaveprop.Radio do
end
defp find_duplicate_contact(changeset) do
s1 = Ecto.Changeset.get_field(changeset, :station1) |> to_string() |> String.upcase()
s2 = Ecto.Changeset.get_field(changeset, :station2) |> to_string() |> String.upcase()
g1 = Ecto.Changeset.get_field(changeset, :grid1) |> to_string() |> String.upcase()
g2 = Ecto.Changeset.get_field(changeset, :grid2) |> to_string() |> String.upcase()
s1 = changeset |> Ecto.Changeset.get_field(:station1) |> to_string() |> String.upcase()
s2 = changeset |> Ecto.Changeset.get_field(:station2) |> to_string() |> String.upcase()
g1 = changeset |> Ecto.Changeset.get_field(:grid1) |> to_string() |> String.upcase()
g2 = changeset |> Ecto.Changeset.get_field(:grid2) |> to_string() |> String.upcase()
band = Ecto.Changeset.get_field(changeset, :band)
ts = Ecto.Changeset.get_field(changeset, :qso_timestamp)

View file

@ -86,7 +86,7 @@ defmodule Microwaveprop.Radio.AdifImport do
# Value starts right after the closing >
# Find the > after the tag
tag_text = String.slice(record_string, tag_start, 999)
value_offset = tag_start + byte_size(String.split(tag_text, ">", parts: 2) |> List.first()) + 1
value_offset = tag_start + (tag_text |> String.split(">", parts: 2) |> List.first() |> byte_size()) + 1
value = String.slice(record_string, value_offset, size)
Map.put(acc, name, value)
@ -229,7 +229,14 @@ defmodule Microwaveprop.Radio.AdifImport do
defp dedupe(rows, db_map) do
{accepted, _in_batch, duplicates} =
Enum.reduce(rows, {[], %{}, []}, fn row, {accepted, in_batch, dups} ->
key = dedup_key(row.attrs["station1"], row.attrs["grid1"], row.attrs["station2"], row.attrs["grid2"], row.attrs["band"])
key =
dedup_key(
row.attrs["station1"],
row.attrs["grid1"],
row.attrs["station2"],
row.attrs["grid2"],
row.attrs["band"]
)
cond do
conflict_in_map?(db_map, key, row.timestamp) ->

View file

@ -170,6 +170,15 @@ defmodule MicrowavepropWeb.AboutLive do
</li>
</ul>
</div>
<div class="rounded-box border border-primary/30 bg-primary/10 p-3 text-center">
<p>
If this tool is useful to you, consider
<a href="https://ntms.org" target="_blank" class="link link-primary font-semibold">
donating to NTMS
</a>
to help keep the project running.
</p>
</div>
</section>
</Layouts.app>
"""

View file

@ -131,6 +131,11 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
</div>
<div class="whitespace-pre-wrap text-sm">{@beacon.notes}</div>
</div>
<div :if={@beacon.user && @beacon.user.callsign} class="mt-3 pt-3 border-t border-base-300">
<span class="text-xs opacity-60">Submitted by</span>
<span class="text-sm font-semibold">{@beacon.user.callsign}</span>
</div>
</div>
</Layouts.app>
"""

View file

@ -5,8 +5,8 @@ defmodule MicrowavepropWeb.SubmitLive do
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Radio.AdifImport
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Radio.CsvImport
alias Microwaveprop.Workers.ContactWeatherEnqueueWorker
@ -355,7 +355,7 @@ defmodule MicrowavepropWeb.SubmitLive do
</div>
<p class="text-sm text-base-content/60 -mt-2">
Be as specific as possible with grid squares (6 characters preferred, e.g. EM12kp).
Be as specific as possible with grid squares (8 characters preferred, e.g. EM12kp37).
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
@ -375,7 +375,13 @@ defmodule MicrowavepropWeb.SubmitLive do
options={@mode_options}
required
/>
<.input field={@form[:qso_timestamp]} type="datetime-local" label="Timestamp (UTC)" lang="en-GB" required />
<.input
field={@form[:qso_timestamp]}
type="datetime-local"
label="Timestamp (UTC)"
lang="en-GB"
required
/>
</div>
<%= if @current_user do %>
@ -416,7 +422,7 @@ defmodule MicrowavepropWeb.SubmitLive do
Timestamps in most formats accepted (e.g. <code>2024-06-15T14:30:00Z</code>, <code>6/15/2024 2:30 PM</code>, <code>2024-06-15 14:30</code>). All times assumed UTC.
</li>
<li>
Grid squares should be as detailed as possible (6 characters preferred, e.g. EM12kp)
Grid squares should be as detailed as possible (8 characters preferred, e.g. EM12kp37)
</li>
<li>Band in MHz (e.g. 10000, 24000)</li>
</ul>
@ -502,9 +508,10 @@ defmodule MicrowavepropWeb.SubmitLive do
</p>
<ul class="list-disc list-inside mt-2 space-y-1 text-base-content/60">
<li>
Required fields: <code>CALL</code>, <code>STATION_CALLSIGN</code> (or <code>OPERATOR</code>),
<code>GRIDSQUARE</code>, <code>MY_GRIDSQUARE</code>, <code>QSO_DATE</code>, <code>TIME_ON</code>,
and <code>FREQ</code> or <code>BAND</code>
Required fields: <code>CALL</code>, <code>STATION_CALLSIGN</code>
(or <code>OPERATOR</code>), <code>GRIDSQUARE</code>, <code>MY_GRIDSQUARE</code>, <code>QSO_DATE</code>, <code>TIME_ON</code>,
and <code>FREQ</code>
or <code>BAND</code>
</li>
<li>Only microwave contacts (902 MHz and up) will be imported</li>
<li>Sub-microwave contacts are silently skipped</li>
@ -512,7 +519,12 @@ defmodule MicrowavepropWeb.SubmitLive do
</ul>
</div>
<form id="adif-upload-form" phx-submit="upload_adif" phx-change="validate_adif" class="space-y-4">
<form
id="adif-upload-form"
phx-submit="upload_adif"
phx-change="validate_adif"
class="space-y-4"
>
<div class="fieldset mb-2">
<label>
<span class="label mb-1">ADIF File</span>

View file

@ -75,7 +75,9 @@ defmodule Microwaveprop.Radio.AdifImportTest do
assert {:ok, preview} = AdifImport.preview(adif, @submitter)
assert [row] = preview.valid
assert row.attrs["band"] == expected_band, "FREQ #{freq} should map to band #{expected_band}, got #{row.attrs["band"]}"
assert row.attrs["band"] == expected_band,
"FREQ #{freq} should map to band #{expected_band}, got #{row.attrs["band"]}"
end
end