diff --git a/README.md b/README.md
index 989d26c..5c658c7 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ If no target org is found, deploy exits with setup guidance.
## Configure Salesforce
-After deploy, open **Setup** and configure the **Vector Map Geocoding** custom setting. This is a **hierarchy** custom setting: use **Manage** to set **organization** defaults so every user gets the same vector tile URL, geocoding provider, and (if needed) NSW Point API key.
+After deploy, open **Setup** and configure the **Vector Map Geocoding** custom setting. This is a **hierarchy** custom setting: use **Manage** to set **organization** defaults so every user gets the same vector tile URL and geocoding provider.
**Where to find it:** **Setup** → **Custom Settings** → **Vector Map Geocoding** → **Manage** (set values at the **Organization** level unless you need per-profile overrides).
@@ -69,12 +69,19 @@ After deploy, open **Setup** and configure the **Vector Map Geocoding** custom s
On the manage screen, create or edit the **default organization level** row. Set:
-- **Geocoding Provider** — `OpenStreetMap` (Nominatim, default), `NSW_Point` (NSW Point geocoding; requires **NSW Point API Key**), or `None` (no external HTTP geocoding; use Salesforce compound Address lat/lng only).
-- **NSW Point API Key** — required only when **Geocoding Provider** is NSW Point; used by Apex as the `x-api-key` header for NSW Point Geocode Address.
+- **Geocoding Provider** — `OpenStreetMap` (Nominatim, default), `NSW_Point` (NSW Point geocoding; requires **NSW Point Named Credential** setup below), or `None` (no external HTTP geocoding; use Salesforce compound Address lat/lng only).
- **Vector Tile Service URL** — optional ArcGIS **VectorTileServer** URL (must end with `VectorTileServer`). Leave blank to use the built-in NSW Spatial Services basemap URL (same default as Apex and the LWC).

+### NSW Point API key (Named Credential)
+
+When **Geocoding Provider** is `NSW_Point`, store the API key in the **NSW Point Geocode** Named Credential (not in Custom Settings):
+
+1. **Setup** → **Named Credentials** → **NSW Point Geocode** → **External Credentials** tab → principal **NSW Point API Key**.
+2. Set the **ApiKey** authentication parameter to your NSW Point API key (used as the `x-api-key` header on callouts).
+3. Assign the **Vector Map NSW Point Geocode** permission set to users who need NSW Point geocoding (grants access to the External Credential principal).
+
The install script should setup remote sites to allow Salesforce to reach these services, but for reference they should look like this:

diff --git a/force-app/main/default/classes/VectorMapGeocodeController.cls b/force-app/main/default/classes/VectorMapGeocodeController.cls
index 5074fd5..f2d892b 100644
--- a/force-app/main/default/classes/VectorMapGeocodeController.cls
+++ b/force-app/main/default/classes/VectorMapGeocodeController.cls
@@ -11,8 +11,8 @@ public with sharing class VectorMapGeocodeController {
private static final String SETTING_PROVIDER_NSW = 'NSW_Point';
/** Custom Setting: no HTTP callouts; compound Address lat/lng only. */
private static final String SETTING_PROVIDER_NONE = 'None';
- private static final String NSW_POINT_GEOCODE =
- 'https://point.digital.nsw.gov.au/v3/api/geocodeAddress';
+ /** Named Credential for NSW Point Geocode Address (x-api-key stored in External Credential). */
+ private static final String NSW_POINT_NAMED_CREDENTIAL = 'NSW_Point_Geocode';
private static final String NOMINATIM_SEARCH =
'https://nominatim.openstreetmap.org/search';
private static final String NOMINATIM_USER_AGENT =
@@ -75,13 +75,7 @@ public with sharing class VectorMapGeocodeController {
return out;
}
if (SETTING_PROVIDER_NSW.equals(providerSetting)) {
- String nswKey = getNswPointApiKey();
- if (String.isBlank(nswKey)) {
- out.errorMessage =
- 'Geocoding Provider is NSW Point but NSW Point API Key is not set in Custom Settings.';
- return out;
- }
- return tryNswPoint(trimmed, nswKey);
+ return tryNswPoint(trimmed);
}
return tryNominatim(trimmed);
@@ -112,17 +106,9 @@ public with sharing class VectorMapGeocodeController {
return SETTING_PROVIDER_OSM;
}
- private static String getNswPointApiKey() {
- Vector_Map_Geocoding__c cfg = Vector_Map_Geocoding__c.getOrgDefaults();
- if (cfg == null || String.isBlank(cfg.NSW_Point_API_Key__c)) {
- return null;
- }
- return cfg.NSW_Point_API_Key__c.trim();
- }
-
- private static GeocodeResult tryNswPoint(String address, String apiKey) {
+ private static GeocodeResult tryNswPoint(String address) {
GeocodeResult out = new GeocodeResult();
- HttpRequest req = buildNswPointGeocodeRequest(address, apiKey);
+ HttpRequest req = buildNswPointGeocodeRequest(address);
HttpResponse res = sendHttpRequest(req);
if (res == null) {
out.errorMessage = 'NSW Point request failed.';
@@ -181,19 +167,19 @@ public with sharing class VectorMapGeocodeController {
}
}
- /** NSW Point Geocode Address: GET with x-api-key header. */
- private static HttpRequest buildNswPointGeocodeRequest(
- String address,
- String apiKey
- ) {
+ /** NSW Point Geocode Address: GET via Named Credential (x-api-key from External Credential). */
+ private static HttpRequest buildNswPointGeocodeRequest(String address) {
String encodedAddress = EncodingUtil.urlEncode(address, 'UTF-8');
- String endpoint = NSW_POINT_GEOCODE + '?address=' + encodedAddress;
+ String endpoint =
+ 'callout:' +
+ NSW_POINT_NAMED_CREDENTIAL +
+ '/v3/api/geocodeAddress?address=' +
+ encodedAddress;
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint);
req.setMethod('GET');
req.setHeader('Accept', 'application/json');
req.setHeader('Accept-Crs', '');
- req.setHeader('x-api-key', apiKey);
req.setTimeout(HTTP_TIMEOUT_MS);
return req;
}
diff --git a/force-app/main/default/classes/VectorMapGeocodeControllerTest.cls b/force-app/main/default/classes/VectorMapGeocodeControllerTest.cls
index 14344aa..d09ff38 100644
--- a/force-app/main/default/classes/VectorMapGeocodeControllerTest.cls
+++ b/force-app/main/default/classes/VectorMapGeocodeControllerTest.cls
@@ -11,10 +11,6 @@ private class VectorMapGeocodeControllerTest {
!req.getEndpoint().contains('subscription-key='),
'NSW Point auth uses x-api-key header, not subscription-key query param'
);
- System.assertEquals(
- 'unit-test-nsw-key',
- req.getHeader('x-api-key')
- );
res.setStatusCode(200);
res.setBody('{"latitude":-33.8688,"longitude":151.2093}');
return res;
@@ -58,6 +54,15 @@ private class VectorMapGeocodeControllerTest {
}
}
+ private class NswHttp401Mock implements HttpCalloutMock {
+ public HttpResponse respond(HttpRequest req) {
+ HttpResponse res = new HttpResponse();
+ res.setStatusCode(401);
+ res.setBody('{"error":"Unauthorized"}');
+ return res;
+ }
+ }
+
@IsTest
static void geocodeAddress_blank_returnsError() {
Test.startTest();
@@ -71,7 +76,7 @@ private class VectorMapGeocodeControllerTest {
@IsTest
static void geocodeAddress_nswPoint_success() {
- insertSettings('unit-test-nsw-key', 'NSW_Point');
+ insertSettings('NSW_Point');
Test.setMock(HttpCalloutMock.class, new NswPointSuccessMock());
Test.startTest();
VectorMapGeocodeController.GeocodeResult r = VectorMapGeocodeController.geocodeAddress(
@@ -86,7 +91,7 @@ private class VectorMapGeocodeControllerTest {
@IsTest
static void geocodeAddress_nswPoint_parsesEsriCandidatesJson() {
- insertSettings('key-esri', 'NSW_Point');
+ insertSettings('NSW_Point');
Test.setMock(HttpCalloutMock.class, new NswPointEsriCandidatesMock());
Test.startTest();
VectorMapGeocodeController.GeocodeResult r = VectorMapGeocodeController.geocodeAddress(
@@ -140,7 +145,7 @@ private class VectorMapGeocodeControllerTest {
@IsTest
static void geocodeAddress_nswPoint_httpError_returnsError() {
- insertSettings('key-for-test', 'NSW_Point');
+ insertSettings('NSW_Point');
Test.setMock(HttpCalloutMock.class, new NswHttp500Mock());
Test.startTest();
VectorMapGeocodeController.GeocodeResult r = VectorMapGeocodeController.geocodeAddress(
@@ -153,12 +158,9 @@ private class VectorMapGeocodeControllerTest {
}
@IsTest
- static void geocodeAddress_nswPoint_noApiKey_returnsError() {
- insert new Vector_Map_Geocoding__c(
- SetupOwnerId = UserInfo.getOrganizationId(),
- Geocoding_Provider__c = 'NSW_Point',
- NSW_Point_API_Key__c = null
- );
+ static void geocodeAddress_nswPoint_unauthorized_returnsError() {
+ insertSettings('NSW_Point');
+ Test.setMock(HttpCalloutMock.class, new NswHttp401Mock());
Test.startTest();
VectorMapGeocodeController.GeocodeResult r = VectorMapGeocodeController.geocodeAddress(
'Sydney'
@@ -166,15 +168,16 @@ private class VectorMapGeocodeControllerTest {
Test.stopTest();
System.assertNotEquals(null, r.errorMessage);
System.assert(
- r.errorMessage.contains('API Key'),
- 'Expected message about API key: ' + r.errorMessage
+ r.errorMessage.contains('401'),
+ 'Expected HTTP 401 in error: ' + r.errorMessage
);
+ System.assertEquals(null, r.latitude);
+ System.assertEquals(null, r.longitude);
}
- private static void insertSettings(String apiKey, String geocodingProvider) {
+ private static void insertSettings(String geocodingProvider) {
insert new Vector_Map_Geocoding__c(
SetupOwnerId = UserInfo.getOrganizationId(),
- NSW_Point_API_Key__c = apiKey,
Geocoding_Provider__c = geocodingProvider
);
}
diff --git a/force-app/main/default/externalCredentials/NSW_Point_Geocode.externalCredential-meta.xml b/force-app/main/default/externalCredentials/NSW_Point_Geocode.externalCredential-meta.xml
new file mode 100644
index 0000000..c1d8f12
--- /dev/null
+++ b/force-app/main/default/externalCredentials/NSW_Point_Geocode.externalCredential-meta.xml
@@ -0,0 +1,24 @@
+
+
External geocoding is on. Pins are placed using coordinates looked up from the address text - (provider and key in Custom Settings — Vector Map Geocoding). Salesforce compound coordinates + (provider in Custom Settings — Vector Map Geocoding; NSW Point API key in Named Credentials — NSW Point Geocode). Salesforce compound coordinates are used as fallback when the lookup returns no result.
diff --git a/force-app/main/default/lwc/vectorMap/vectorMap.js-meta.xml b/force-app/main/default/lwc/vectorMap/vectorMap.js-meta.xml index 83c813e..5b3456a 100644 --- a/force-app/main/default/lwc/vectorMap/vectorMap.js-meta.xml +++ b/force-app/main/default/lwc/vectorMap/vectorMap.js-meta.xml @@ -42,7 +42,7 @@ type="Boolean" label="External geocoding" default="true" - description="When true (recommended), geocode from the displayed address text via Apex; coordinates override Salesforce compound lat/lng. Provider and NSW Point API key: Setup — Custom Settings — Vector Map Geocoding. Turn off to use only Salesforce Address coordinates. Respect Nominatim usage policy when using OpenStreetMap." + description="When true (recommended), geocode from the displayed address text via Apex; coordinates override Salesforce compound lat/lng. Provider: Setup — Custom Settings — Vector Map Geocoding. NSW Point API key: Setup — Named Credentials — NSW Point Geocode. Turn off to use only Salesforce Address coordinates. Respect Nominatim usage policy when using OpenStreetMap." />