Replace frontend Google Maps key with server-side proxy#849
Open
edwh wants to merge 29 commits into
Open
Conversation
Newer php:8.2-fpm base ships docker.conf instead of zz-docker.conf. The old code only removed zz-docker.conf, so docker.conf's listen=9000 override survived, making PHP-FPM bind to TCP while nginx expected the unix socket — causing 502 Bad Gateway on every deploy. Also, docker.conf carries clear_env=no which is required for Fly.io secrets to reach PHP-FPM workers. The previous approach of deleting the file would have stripped that setting. Fix: patch docker.conf in-place (keep clear_env=no, set unix socket) instead of deleting it. Also remove zz-docker.conf for old images. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --default-character-set=utf8mb4 to both mysqldump and mysql import commands, and set CHARACTER SET utf8mb4 on CREATE DATABASE. Without these flags, mysqldump silently encodes 4-byte characters (emoji) as latin1, corrupting them to literal '?' on import. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The notifications table has 488k rows with top users having 57k+ entries. Without created_at in the index the ORDER BY in Laravel's notification queries forces a filesort over all per-user rows, causing multi-second queries that overload the single-CPU DB VM. Index already applied directly to production on 2026-05-12. Migration guards against re-adding to avoid error on prod. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User.notifications() override adds WHERE created_at >= now()-1year so all notification queries (including unreadNotifications()) are bounded. Without this, users with years of history triggered filesort over 100k+ rows per page load. navbar: load with ->take(10)->get() (LIMIT 10 in DB) instead of loading the full collection and taking in PHP. API UserController: ->unreadNotifications()->count() instead of loading the collection into memory and counting in PHP (COUNT(*) vs SELECT *). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r bug - fly.toml: performance-2x (dedicated CPUs, avoids shared throttling) - fly-mysql.toml: performance-2x + 4096MB; add --innodb_buffer_pool_size=2800M so InnoDB uses ~70% of RAM instead of Docker default 128MB - startup.sh: view:clear before view:cache so stale compiled views from a previous image don't persist across deploys - navbar.blade.php: multi-line @php/@endphp form — single-line form silently fails to compile @php directive on this Blade version, leaving a bare @php literal in the output and causing Undefined variable $navbarNotifications Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fly-mysql.toml: add --log_bin_trust_function_creators=ON to mysqld flags so it persists across DB restarts (no deploy needed for the live SET GLOBAL already applied) - fly-migrate.sh: SET GLOBAL log_bin_trust_function_creators = 1 via root before each import so non-SUPER restarters user can create functions/triggers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gging) Dockerfile.mysql already has procps; this adds it to the app container so vmstat/top/ps are available when SSH'd into the restarters machine. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was incorrectly disabled, causing MediawikiApi class-not-found errors in production logs on every request that touched the wiki code path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rue) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents timeouts on slow DB queries during cold starts or heavy load. request_terminate_timeout already set to 120s in php-fpm www.conf. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…copy
- Use config('filesystems.default') to determine disk (s3 on Fly, public_uploads in dev)
- Store logo to the correct disk instead of always using public_uploads (ephemeral on Fly)
- Generate _x100 sized copy on the same disk for views that use Network::sizedLogo('_x100')
- Fixes logo uploads disappearing on Fly.io restart and _x100 version not being available
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e counts - ApiController::homepage_data: replace Party::past()->get() (18k rows into PHP) with a single SQL aggregate for participants + hours_volunteered. hoursVolunteered() formula expressed as CASE/CEIL(TIMESTAMPDIFF) in SQL. Add Cache::lock() to prevent thundering herd on cold cache. - Device::fixedPoweredCount/fixedUnpoweredCount/poweredCount/unpoweredCount: replace withCount()->get() over 215k rows with COUNT(*)+JOIN queries. Each was loading the entire devices table into PHP just to sum a count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ner) AddUserToDiscourseThreadForEvent uses pcntl_signal() for timeout handling. Without pcntl the jobs fail immediately and land in failed_jobs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Package addwiki/mediawiki-api was upgraded to v3.1.0 which renamed its namespace from Mediawiki\Api\ to Addwiki\Mediawiki\Api\. Update all three files that reference the old namespace and migrate FluentRequest to ActionRequest, ApiUser to UserAndPassword auth, and the login flow to use MediaWiki::newFromEndpoint() with constructor-time auth. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was using a custom fxm.php path; xmlrpc.php is the correct standard endpoint. WP_XMLRPC_PSWD secret also needs trailing dot removed on next deploy: fly secrets set WP_XMLRPC_PSWD='giannutri15Stone$87' Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d CORS for /api/
therestartproject.org embeds /outbound/info/group/{id} in an iframe and calls
/api/group/{id}/stats cross-origin. Fix by:
- nginx: map X-Frame-Options to empty string for /outbound/ routes (omits header)
- Laravel: AddCorsHeaders middleware added to api middleware group
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LogInToWiki: explicit CookieJar + ActionApi constructor injection
replaces getConfig('cookies') which silently returns bool in Guzzle 7
- fly.toml: WP_XMLRPC_ENDPOINT uses /fxm.php (custom alias on live site)
not /xmlrpc.php which is blocked by Wordfence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pcntl extension is not available in PHP-FPM on Fly.io, causing all AddUserToDiscourseThreadForEvent queue jobs to fail with "Call to undefined function pcntl_signal()". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pcntl_signal/pcntl_alarm were never working correctly: pcntl_signal threw "undefined function" in some environments, and when available $this->fail() threw "undefined method" as InteractsWithQueue trait was not used. $tries = 1 already limits retries without needing a signal-based timeout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs: (1) InteractsWithQueue was imported as the Contracts interface not used as a trait, so $this->fail() was undefined. (2) pcntl_signal is not available in all environments. Fix: use the Queue trait for fail() support, guard pcntl calls with function_exists. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$event->theGroup->archived_at was accessed before the $event null guard, causing failures when events or groups had been deleted (timing windows). Move the archived_at check inside the $event && $user guard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The block form @php...@endphp is not compiled correctly by the Blade version on this container: @php is emitted as literal text while @endphp compiles to ?>, leaving $navbarNotifications undefined at runtime (500). The parenthesised @php($expr) form compiles correctly everywhere. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
castBooleansToString(string|bool) rejects null in PHP 8, thrown when $user->biography is null (nullable column). Simplest fix: set 'bio' to null in discourse config so the field is skipped entirely. Also updates live-patch-status.md with navbar @php fix notes and DISCOURSE_SECRET putenv workaround (system env had D$ truncated at #). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
castBooleansToString(string|bool) rejects null in PHP 8, thrown when $user->biography is null (nullable column). Adding a getBiographyAttribute accessor ensures the field always resolves to a string, so bio syncing works correctly without skipping the field entirely. Reverts the 'bio' => null workaround from the previous commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document bootstrap/app.php monkey-patch (sets $_ENV/$_SERVER/putenv for all broken fly secrets — PHP-FPM clears env so only CLI/queue was affected) and AddUserToDiscourseThreadForEvent.php pcntl fix (146 failed jobs retried). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A queue job making a blocking HTTP call (no timeout, no pcntl_alarm available on this container) can hang indefinitely and block the entire queue. The watchdog runs every minute, checks for jobs reserved >120s, and kills the worker — supervisord restarts it automatically. Permanent fix lands on next deploy (pcntl is in Dockerfile.fly). startup.sh re-registers the cron entry on restart so it works before then too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates the GOOGLE_API_CONSOLE_KEY from frontend HTML entirely. Places Autocomplete and Place Details calls are now proxied through authenticated Laravel endpoints (/maps/autocomplete, /maps/place-details) using the existing IP-restricted backend key. - Add MapsProxyController with autocomplete and placeDetails methods - Add /maps/* routes inside the auth+verifyUserConsent middleware group - Replace vue-google-autocomplete with PlacesAutocomplete.vue (custom component that calls the proxy instead of the Google JS SDK) - Update VenueAddress.vue and GroupLocation.vue to use PlacesAutocomplete - Remove Google Maps JS SDK load from gmap.blade.php - Fix Handler.php to return 422 (not 500) for ValidationException in JSON requests - Add OpenAPI definitions for the two new endpoints - Add feature tests (MapsProxyTest) covering auth, success, and validation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Comment on lines
+14
to
+15
| return response('', 200) | ||
| ->header('Access-Control-Allow-Origin', '*') |
| } | ||
|
|
||
| $response = $next($request); | ||
| $response->headers->set('Access-Control-Allow-Origin', '*'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Summary
GOOGLE_API_CONSOLE_KEYfrom frontend HTML entirely/maps/autocomplete,/maps/place-details) that proxy Google Places API calls server-side using the existing IP-restricted backend keyvue-google-autocomplete(which required the JS SDK + frontend key) with a customPlacesAutocomplete.vuethat calls the proxygmap.blade.phpGoogle Maps JS SDK includeWhy this is safer than HTTP referrer restrictions
HTTP referrer restrictions (as in PR #848) are unreliable — browsers frequently strip or modify the
Refererheader. This approach eliminates the attack surface entirely: no key appears in any HTML page.The endpoints sit behind the
auth + verifyUserConsentmiddleware, so only logged-in, consent-verified users can trigger Google API calls. This matches the actual usage — group and event create/edit pages already require authentication.What changed
app/Http/Controllers/MapsProxyController.phproutes/web.php/maps/autocompleteand/maps/place-detailsroutes under auth middlewareresources/js/components/PlacesAutocomplete.vuevue-google-autocomplete, calls proxyresources/js/components/VenueAddress.vuevue-google-autocomplete→PlacesAutocompleteresources/js/components/GroupLocation.vuevue-google-autocomplete→PlacesAutocompleteresources/views/includes/gmap.blade.phpapp/Exceptions/Handler.phpValidationExceptionon JSON requestsstorage/api-docs/api-docs.jsontests/Feature/Maps/MapsProxyTest.phpTest plan
MapsProxyTest— 6 tests, all passing (auth redirect, autocomplete with faked HTTP, place details with faked HTTP, validation errors)GOOGLE_API_CONSOLE_KEYappears in page source on group/event edit pages🤖 Generated with Claude Code