mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-14 15:45:35 +00:00
Add public repeater checkbox when adding repeater
This commit is contained in:
@@ -149,6 +149,54 @@ func TestRepeaterCrudPosts(t *testing.T) {
|
||||
assertRedirect(t, delRep, "/", "delete repeater")
|
||||
}
|
||||
|
||||
// TestAddRepeaterExposePublicPage: the add form's "Publish a public page"
|
||||
// checkbox is honored at creation (not only on the edit page). Present → the
|
||||
// repeater publishes a public page; absent → it does not.
|
||||
func TestAddRepeaterExposePublicPage(t *testing.T) {
|
||||
t.Parallel()
|
||||
st, ctx, ts, h := splitServer(t)
|
||||
_, sess := appLogin(t, ts, st, ctx, h.app, "exposer")
|
||||
|
||||
exposed := func(t *testing.T, values url.Values) bool {
|
||||
t.Helper()
|
||||
resp := post(t, ts, h.app, "/repeaters", values, sess)
|
||||
resp.Body.Close()
|
||||
loc, _ := url.Parse(resp.Header.Get("Location"))
|
||||
parts := strings.Split(loc.Path, "/")
|
||||
if resp.StatusCode != http.StatusSeeOther || len(parts) < 3 {
|
||||
t.Fatalf("create repeater = %d %q, want 303 → /repeaters/{id}/added", resp.StatusCode, resp.Header.Get("Location"))
|
||||
}
|
||||
var got bool
|
||||
if err := st.Pool().QueryRow(ctx,
|
||||
`SELECT expose_public_page FROM repeaters WHERE public_id = $1`, parts[2]).Scan(&got); err != nil {
|
||||
t.Fatalf("read expose_public_page: %v", err)
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
radio := func(v url.Values) url.Values {
|
||||
v.Set("radio_freq_mhz", "869.525")
|
||||
v.Set("radio_bw_khz", "250")
|
||||
v.Set("radio_sf", "11")
|
||||
v.Set("radio_cr", "5")
|
||||
return v
|
||||
}
|
||||
|
||||
onID, _ := meshcore.GenerateLocalIdentity(rand.Reader)
|
||||
if !exposed(t, radio(url.Values{
|
||||
"name": {"Public Rep"}, "public_key": {onID.String()}, "expose_public_page": {"1"},
|
||||
})) {
|
||||
t.Error("expose_public_page=1 at add time did not publish a public page")
|
||||
}
|
||||
|
||||
offID, _ := meshcore.GenerateLocalIdentity(rand.Reader)
|
||||
if exposed(t, radio(url.Values{
|
||||
"name": {"Private Rep"}, "public_key": {offID.String()},
|
||||
})) {
|
||||
t.Error("repeater added without the checkbox should not publish a public page")
|
||||
}
|
||||
}
|
||||
|
||||
// #81 create link, #82 delete link, #85 share commands, #86 steward, #83 unshare,
|
||||
// #87 org participation.
|
||||
func TestRepeaterSharePosts(t *testing.T) {
|
||||
|
||||
@@ -250,14 +250,15 @@ func (s *Handlers) handleSetupComplete(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
rep, err := s.Store.CreateRepeater(r.Context(), &store.Repeater{
|
||||
OwnerID: uid,
|
||||
Name: name,
|
||||
PublicKeyHex: pubHex,
|
||||
RadioFreqHz: freq,
|
||||
RadioBwHz: bw,
|
||||
RadioSF: int16(sf),
|
||||
RadioCR: int16(cr),
|
||||
ShowOnPublicOrg: r.FormValue("show_on_public_org") != "",
|
||||
OwnerID: uid,
|
||||
Name: name,
|
||||
PublicKeyHex: pubHex,
|
||||
RadioFreqHz: freq,
|
||||
RadioBwHz: bw,
|
||||
RadioSF: int16(sf),
|
||||
RadioCR: int16(cr),
|
||||
ShowOnPublicOrg: r.FormValue("show_on_public_org") != "",
|
||||
ExposePublicPage: r.FormValue("expose_public_page") != "",
|
||||
})
|
||||
if errors.Is(err, store.ErrDuplicate) {
|
||||
http.Error(w, "you already added a repeater with that public key", http.StatusConflict)
|
||||
|
||||
@@ -131,7 +131,7 @@ func TestSerialSetupFlow(t *testing.T) {
|
||||
complete := url.Values{
|
||||
"name": {"Hilltop"}, "public_key": {pub},
|
||||
"radio_freq_mhz": {"910.525"}, "radio_bw_khz": {"62.5"}, "radio_sf": {"7"}, "radio_cr": {"5"},
|
||||
"lat": {"15.0"}, "lon": {"35.0"},
|
||||
"lat": {"15.0"}, "lon": {"35.0"}, "expose_public_page": {"1"},
|
||||
}
|
||||
cResp := post(t, ts, h.app, "/repeaters/setup/complete", complete, sess)
|
||||
if cResp.StatusCode != http.StatusOK {
|
||||
@@ -160,6 +160,9 @@ func TestSerialSetupFlow(t *testing.T) {
|
||||
if got.Latitude == nil || got.Longitude == nil || *got.Latitude != 15.0 || *got.Longitude != 35.0 {
|
||||
t.Errorf("location not stored: %v, %v", got.Latitude, got.Longitude)
|
||||
}
|
||||
if !got.ExposePublicPage {
|
||||
t.Error("expose_public_page from the setup form was not persisted")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildSetupCommands locks the from-scratch command order (header → profile
|
||||
|
||||
@@ -201,14 +201,15 @@ func (s *Handlers) handleAddRepeater(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
rep, err := s.Store.CreateRepeater(r.Context(), &store.Repeater{
|
||||
OwnerID: uid,
|
||||
Name: name,
|
||||
PublicKeyHex: pubHex,
|
||||
RadioFreqHz: freq,
|
||||
RadioBwHz: bw,
|
||||
RadioSF: int16(sf),
|
||||
RadioCR: int16(cr),
|
||||
ShowOnPublicOrg: r.FormValue("show_on_public_org") != "",
|
||||
OwnerID: uid,
|
||||
Name: name,
|
||||
PublicKeyHex: pubHex,
|
||||
RadioFreqHz: freq,
|
||||
RadioBwHz: bw,
|
||||
RadioSF: int16(sf),
|
||||
RadioCR: int16(cr),
|
||||
ShowOnPublicOrg: r.FormValue("show_on_public_org") != "",
|
||||
ExposePublicPage: r.FormValue("expose_public_page") != "",
|
||||
})
|
||||
if errors.Is(err, store.ErrDuplicate) {
|
||||
addErr(w, r, "You already added a repeater with that public key.")
|
||||
|
||||
@@ -237,6 +237,10 @@
|
||||
<input class="form-check-input" type="checkbox" id="show_on_public_org">
|
||||
<span class="form-check-label">Show this repeater on your organizations' <strong>public map</strong>.</span>
|
||||
</label>
|
||||
<label class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" id="expose_public_page">
|
||||
<span class="form-check-label">Publish a <strong>public page</strong> for this repeater (the NFC/QR target) with its docs, stewards, and location.</span>
|
||||
</label>
|
||||
|
||||
<div class="btn-list mt-3">
|
||||
<button type="button" class="btn btn-primary" id="generate">Generate command list →</button>
|
||||
@@ -313,6 +317,10 @@
|
||||
<input class="form-check-input" type="checkbox" name="show_on_public_org" value="1">
|
||||
<span class="form-check-label">Show this repeater on your organizations' <strong>public map</strong>.</span>
|
||||
</label>
|
||||
<label class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" name="expose_public_page" value="1">
|
||||
<span class="form-check-label">Publish a <strong>public page</strong> for this repeater (the NFC/QR target) with its docs, stewards, and location.</span>
|
||||
</label>
|
||||
|
||||
<div class="btn-list mt-3">
|
||||
<button type="submit" class="btn btn-primary">Add repeater</button>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//go:build browser
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cdplog "github.com/chromedp/cdproto/log"
|
||||
"github.com/chromedp/cdproto/network"
|
||||
"github.com/chromedp/chromedp"
|
||||
)
|
||||
|
||||
// TestE2EAddRepeaterPublicPageOption verifies the "Publish a public page"
|
||||
// checkbox is offered on both add-repeater detail steps — the add-existing (kiss)
|
||||
// form field and the USB-setup (serial) toggle that serial-setup.js reads — so
|
||||
// the option isn't edit-page-only. Both steps must render clean under the CSP.
|
||||
func TestE2EAddRepeaterPublicPageOption(t *testing.T) {
|
||||
srv := newE2EServer(t)
|
||||
_, cookie := srv.login(t, "pager")
|
||||
|
||||
bctx, cancel, watch := startBrowser(t)
|
||||
defer cancel()
|
||||
|
||||
cases := []struct{ name, url, selector string }{
|
||||
{"add-existing", "/repeaters/add?step=details&method=kiss", `input[name="expose_public_page"]`},
|
||||
{"usb-setup", "/repeaters/add?step=serial&method=serial", `#expose_public_page`},
|
||||
}
|
||||
for _, c := range cases {
|
||||
runCtx, cancelRun := context.WithTimeout(bctx, 30*time.Second)
|
||||
// WaitVisible on the checkbox itself proves it rendered and is visible.
|
||||
err := chromedp.Run(runCtx,
|
||||
network.Enable(),
|
||||
cdplog.Enable(),
|
||||
setSessionCookie(cookie),
|
||||
chromedp.Navigate(srv.appURL+c.url),
|
||||
chromedp.WaitVisible(c.selector, chromedp.ByQuery),
|
||||
)
|
||||
cancelRun()
|
||||
if err != nil {
|
||||
t.Errorf("%s step: public-page checkbox (%s) not visible: %v", c.name, c.selector, err)
|
||||
}
|
||||
}
|
||||
|
||||
watch.assertClean(t)
|
||||
}
|
||||
@@ -87,12 +87,12 @@ func (s *Store) CreateRepeater(ctx context.Context, r *Repeater) (*Repeater, err
|
||||
}
|
||||
var out Repeater
|
||||
err = s.pool.QueryRow(ctx, `
|
||||
INSERT INTO repeaters (public_id, owner_id, name, public_key_hex, radio_freq_hz, radio_bw_hz, radio_sf, radio_cr, show_on_public_org)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id, public_id, owner_id, name, public_key_hex, radio_freq_hz, radio_bw_hz, radio_sf, radio_cr, confirmed, confirmed_at, created_at, show_on_public_org`,
|
||||
publicID, r.OwnerID, r.Name, r.PublicKeyHex, r.RadioFreqHz, r.RadioBwHz, r.RadioSF, r.RadioCR, r.ShowOnPublicOrg).
|
||||
INSERT INTO repeaters (public_id, owner_id, name, public_key_hex, radio_freq_hz, radio_bw_hz, radio_sf, radio_cr, show_on_public_org, expose_public_page)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
RETURNING id, public_id, owner_id, name, public_key_hex, radio_freq_hz, radio_bw_hz, radio_sf, radio_cr, confirmed, confirmed_at, created_at, show_on_public_org, expose_public_page`,
|
||||
publicID, r.OwnerID, r.Name, r.PublicKeyHex, r.RadioFreqHz, r.RadioBwHz, r.RadioSF, r.RadioCR, r.ShowOnPublicOrg, r.ExposePublicPage).
|
||||
Scan(&out.ID, &out.PublicID, &out.OwnerID, &out.Name, &out.PublicKeyHex, &out.RadioFreqHz, &out.RadioBwHz,
|
||||
&out.RadioSF, &out.RadioCR, &out.Confirmed, &out.ConfirmedAt, &out.CreatedAt, &out.ShowOnPublicOrg)
|
||||
&out.RadioSF, &out.RadioCR, &out.Confirmed, &out.ConfirmedAt, &out.CreatedAt, &out.ShowOnPublicOrg, &out.ExposePublicPage)
|
||||
if isUniqueViolation(err) {
|
||||
return nil, ErrDuplicate
|
||||
}
|
||||
|
||||
@@ -320,7 +320,11 @@
|
||||
form.set("lat", loc.lat.toFixed(6));
|
||||
form.set("lon", loc.lon.toFixed(6));
|
||||
}
|
||||
if ($("show_on_public_org").checked) form.set("show_on_public_org", "1");
|
||||
// Optional visibility toggles — guarded so a trimmed-down template can omit them.
|
||||
var showOrg = $("show_on_public_org");
|
||||
if (showOrg && showOrg.checked) form.set("show_on_public_org", "1");
|
||||
var exposePage = $("expose_public_page");
|
||||
if (exposePage && exposePage.checked) form.set("expose_public_page", "1");
|
||||
var resp = await fetch("/repeaters/setup/complete", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
|
||||
Reference in New Issue
Block a user