fix: close failed location sessions and harden upgrade rehearsal

This commit is contained in:
SimpleTest 2026-07-23 02:34:45 +03:00
parent 2159e6cda7
commit f0512fab84
4 changed files with 123 additions and 25 deletions

View File

@ -1630,12 +1630,16 @@ export const Hooks = {
if (typeof window.WhoNeedHelpAndroid?.postMessage === "function") { if (typeof window.WhoNeedHelpAndroid?.postMessage === "function") {
this.nativeError = () => { this.nativeError = () => {
this.pushEvent("location-error", {}) this.pushEvent("location-error", {})
this.pushEvent("stop-tracking", {})
} }
window.addEventListener("wnh:native-tracking-error", this.nativeError) window.addEventListener("wnh:native-tracking-error", this.nativeError)
return return
} }
if (!navigator.geolocation) {
this.pushEvent("location-error", {})
return
}
this.watchId = navigator.geolocation.watchPosition( this.watchId = navigator.geolocation.watchPosition(
position => this.queueLocation({ position => this.queueLocation({
latitude: position.coords.latitude, latitude: position.coords.latitude,

View File

@ -288,18 +288,19 @@ defmodule WhoNeedHelpWeb.RequestLive.Show do
end end
def handle_event("location-error", _, socket) do def handle_event("location-error", _, socket) do
socket =
case stop_tracking(socket) do
{:ok, stopped_socket} -> stopped_socket
{:error, _reason} -> socket
end
{:noreply, put_flash(socket, :error, gettext("The browser could not share your location."))} {:noreply, put_flash(socket, :error, gettext("The browser could not share your location."))}
end end
def handle_event("stop-tracking", _, socket) do def handle_event("stop-tracking", _, socket) do
case Tracking.stop_session(socket.assigns.current_scope, socket.assigns.assignment) do case stop_tracking(socket) do
{:ok, _} -> {:ok, stopped_socket} ->
{:noreply, {:noreply, stopped_socket}
socket
|> assign(:tracking_active, false)
|> assign(:tracking_session_id, nil)
|> maybe_untrack_browser_presence()
|> maybe_stop_native_tracking()}
{:error, reason} -> {:error, reason} ->
{:noreply, put_flash(socket, :error, message(reason))} {:noreply, put_flash(socket, :error, message(reason))}
@ -470,6 +471,21 @@ defmodule WhoNeedHelpWeb.RequestLive.Show do
defp maybe_untrack_browser_presence(socket), do: socket defp maybe_untrack_browser_presence(socket), do: socket
defp stop_tracking(socket) do
case Tracking.stop_session(socket.assigns.current_scope, socket.assigns.assignment) do
{:ok, _} ->
{:ok,
socket
|> assign(:tracking_active, false)
|> assign(:tracking_session_id, nil)
|> maybe_untrack_browser_presence()
|> maybe_stop_native_tracking()}
{:error, reason} ->
{:error, reason}
end
end
defp transition(socket, fun, success) do defp transition(socket, fun, success) do
case fun.(socket.assigns.current_scope, socket.assigns.assignment.id) do case fun.(socket.assigns.current_scope, socket.assigns.assignment.id) do
{:ok, _} -> {:noreply, put_flash(socket, :info, success)} {:ok, _} -> {:noreply, put_flash(socket, :info, success)}

View File

@ -50,6 +50,15 @@ rehearsal_map_tile_url=$(
'. "$1"; printf %s "${MAP_TILE_URL:-https://tile.openstreetmap.org/{z}/{x}/{y}.png}"' \ '. "$1"; printf %s "${MAP_TILE_URL:-https://tile.openstreetmap.org/{z}/{x}/{y}.png}"' \
sh "$PUBLIC_ENV_FILE" sh "$PUBLIC_ENV_FILE"
) )
rehearsal_postgis_image=$(
sh -c '. "$1"; printf %s "${POSTGIS_IMAGE:?POSTGIS_IMAGE is missing from .env}"' \
sh "$PUBLIC_ENV_FILE"
)
rehearsal_socket_proxy_image=$(
sh -c \
'. "$1"; printf %s "${SOCKET_PROXY_IMAGE:?SOCKET_PROXY_IMAGE is missing from .env}"' \
sh "$PUBLIC_ENV_FILE"
)
set -a set -a
# shellcheck source=/dev/null # shellcheck source=/dev/null
@ -112,11 +121,17 @@ export PHX_HOST="$rehearsal_phx_host"
export PHX_SCHEME="$rehearsal_phx_scheme" export PHX_SCHEME="$rehearsal_phx_scheme"
export PHX_URL_PORT="$rehearsal_phx_url_port" export PHX_URL_PORT="$rehearsal_phx_url_port"
export MAP_TILE_URL="$rehearsal_map_tile_url" export MAP_TILE_URL="$rehearsal_map_tile_url"
export POSTGIS_IMAGE="$rehearsal_postgis_image"
export SOCKET_PROXY_IMAGE="$rehearsal_socket_proxy_image"
export TRAEFIK_TRUSTED_IPS=127.0.0.1/32 export TRAEFIK_TRUSTED_IPS=127.0.0.1/32
export TRAEFIK_PROJECT_CONSTRAINT="$project" export TRAEFIK_PROJECT_CONSTRAINT="$project"
export TRAEFIK_APP_NAME="wnh-upgrade-rehearsal-$compact_id" export TRAEFIK_APP_NAME="wnh-upgrade-rehearsal-$compact_id"
export TRAEFIK_DOCKER_NETWORK="${project}_ingress" export TRAEFIK_DOCKER_NETWORK="${project}_ingress"
export REHEARSAL_IMAGE="who-need-help:upgrade-rehearsal-$run_id" export REHEARSAL_IMAGE="who-need-help:upgrade-rehearsal-$run_id"
# compose.yaml validates APP_IMAGE before applying the rehearsal override.
# Point both variables at the same unique image so config validation, the
# build, and cleanup all address only this rehearsal run.
export APP_IMAGE="$REHEARSAL_IMAGE"
compose() { compose() {
docker compose \ docker compose \
@ -173,9 +188,9 @@ cleanup() {
} }
trap cleanup EXIT HUP INT TERM trap cleanup EXIT HUP INT TERM
database_snapshot() { database_table_list() {
target=$1 target=$1
tables=$(
compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$rehearsal_db" \ psql --username "$POSTGRES_USER" --dbname "$rehearsal_db" \
--tuples-only --no-align \ --tuples-only --no-align \
@ -185,12 +200,17 @@ database_snapshot() {
WHERE schemaname = 'public' WHERE schemaname = 'public'
AND tablename NOT IN ('oban_jobs', 'schema_migrations', 'spatial_ref_sys') AND tablename NOT IN ('oban_jobs', 'schema_migrations', 'spatial_ref_sys')
ORDER BY tablename; ORDER BY tablename;
" " >"$target"
) }
database_snapshot() {
target=$1
table_list=$2
: >"$target" : >"$target"
for table in $tables; do while IFS= read -r table; do
[ -n "$table" ] || continue
row_count=$( row_count=$(
compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$rehearsal_db" \ psql --username "$POSTGRES_USER" --dbname "$rehearsal_db" \
@ -198,7 +218,7 @@ database_snapshot() {
--command "SELECT count(*) FROM \"$table\";" --command "SELECT count(*) FROM \"$table\";"
) )
printf '%s %s\n' "$table" "$row_count" >>"$target" printf '%s %s\n' "$table" "$row_count" >>"$target"
done done <"$table_list"
} }
compose config --quiet compose config --quiet
@ -221,7 +241,10 @@ compose exec -T db \
compose exec -T db pg_restore --list <"$dump" >"$output_dir/pg-restore-list.txt" compose exec -T db pg_restore --list <"$dump" >"$output_dir/pg-restore-list.txt"
database_snapshot "$output_dir/database-before.txt" database_table_list "$output_dir/database-tables-before.txt"
database_snapshot \
"$output_dir/database-before.txt" \
"$output_dir/database-tables-before.txt"
compose exec -T db \ compose exec -T db \
psql --username "$POSTGRES_USER" --dbname "$rehearsal_db" \ psql --username "$POSTGRES_USER" --dbname "$rehearsal_db" \
--tuples-only --no-align \ --tuples-only --no-align \
@ -230,7 +253,9 @@ compose exec -T db \
compose up --detach --wait mailpit migrate proxy web worker compose up --detach --wait mailpit migrate proxy web worker
database_snapshot "$output_dir/database-after.txt" database_snapshot \
"$output_dir/database-after.txt" \
"$output_dir/database-tables-before.txt"
diff -u "$output_dir/database-before.txt" "$output_dir/database-after.txt" \ diff -u "$output_dir/database-before.txt" "$output_dir/database-after.txt" \
>"$output_dir/database-diff.txt" >"$output_dir/database-diff.txt"

View File

@ -924,6 +924,59 @@ defmodule WhoNeedHelpWeb.MutualAidLiveTest do
stop_live_view(helper_view) stop_live_view(helper_view)
end end
test "browser location errors stop the empty tracking session", _context do
category = Catalog.seed_defaults()
requester = user_fixture(display_name: "Location requester")
helper = user_fixture(display_name: "Location helper")
{:ok, request} =
Help.create_request(user_scope_fixture(requester), request_attrs(category))
{:ok, assignment} = Help.accept_request(user_scope_fixture(helper), request.id)
helper_conn =
build_conn()
|> log_in_user(helper)
{:ok, helper_view, _html} = live(helper_conn, ~p"/requests/#{request.id}")
helper_view
|> element("button[phx-click='start-tracking']")
|> render_click()
assert Repo.get_by!(WhoNeedHelp.Tracking.TrackingSession,
assignment_id: assignment.id,
user_id: helper.id,
active: true
)
html = render_hook(helper_view, "location-error", %{})
assert html =~ "The browser could not share your location."
assert has_element?(helper_view, "button[phx-click='start-tracking']")
refute has_element?(helper_view, "button[phx-click='stop-tracking']")
refute Repo.get_by(WhoNeedHelp.Tracking.TrackingSession,
assignment_id: assignment.id,
user_id: helper.id,
active: true
)
stopped_session =
Repo.get_by!(WhoNeedHelp.Tracking.TrackingSession,
assignment_id: assignment.id,
user_id: helper.id
)
assert stopped_session.ended_at
assert stopped_session.sample_count == 0
refute Repo.get_by(WhoNeedHelp.Tracking.Position,
tracking_session_id: stopped_session.id
)
stop_live_view(helper_view)
end
test "regular users cannot enter moderation", %{conn: conn} do test "regular users cannot enter moderation", %{conn: conn} do
assert {:error, {:redirect, %{to: "/requests"}}} = live(conn, ~p"/moderation") assert {:error, {:redirect, %{to: "/requests"}}} = live(conn, ~p"/moderation")
end end