From bdc347a04fcbc5d1e440dca9b1637c0e374103c0 Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Tue, 5 Dec 2017 08:18:52 +0100 Subject: [PATCH] implement #366, add here maps support (#367) * #366 initial work for supporting HERE maps * #366 additional work for handling HERE map in the admin * #366 additional work for handling HERE map in the admin when editing/creating an event * #366 refactor, add map configuration section in basic settings and system page, remove possibility to override the map configuration in the other conf page --- .../java/alfio/config/MvcConfiguration.java | 2 +- .../alfio/controller/EventController.java | 11 +- .../api/admin/LocationApiController.java | 54 +++++-- .../support/LocationDescriptor.java | 57 +++++++- .../alfio/model/system/ConfigurationKeys.java | 12 +- .../configuration/basic-settings.html | 29 ++++ .../admin/partials/configuration/system.html | 28 ++++ .../feature/configuration/configuration.js | 16 +++ .../resources/js/admin/service/service.js | 136 ++++++++++++------ .../support/LocationDescriptorTest.java | 53 ++++++- 10 files changed, 326 insertions(+), 72 deletions(-) diff --git a/src/main/java/alfio/config/MvcConfiguration.java b/src/main/java/alfio/config/MvcConfiguration.java index cafb633260..080edf10d9 100644 --- a/src/main/java/alfio/config/MvcConfiguration.java +++ b/src/main/java/alfio/config/MvcConfiguration.java @@ -230,7 +230,7 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response, + " frame-src 'self' https://js.stripe.com https://www.google.com;" + " font-src 'self';"// + " media-src blob: 'self';"//for loading camera api - + " connect-src 'self' https://api.stripe.com https://maps.googleapis.com/;" //<- currently stripe.js use jsonp but if they switch to xmlhttprequest+cors we will be ready + + " connect-src 'self' https://api.stripe.com https://maps.googleapis.com/ https://geocoder.cit.api.here.com;" //<- currently stripe.js use jsonp but if they switch to xmlhttprequest+cors we will be ready + (environment.acceptsProfiles(Initializer.PROFILE_DEBUG_CSP) ? " report-uri /report-csp-violation" : "")); } }; diff --git a/src/main/java/alfio/controller/EventController.java b/src/main/java/alfio/controller/EventController.java index 04f5e5cf8d..aa05ea04d1 100644 --- a/src/main/java/alfio/controller/EventController.java +++ b/src/main/java/alfio/controller/EventController.java @@ -190,8 +190,15 @@ public String showEvent(@PathVariable("eventName") String eventName, .collect(Collectors.toList()); // - LocationDescriptor ld = LocationDescriptor.fromGeoData(event.getLatLong(), TimeZone.getTimeZone(event.getTimeZone()), - configurationManager.getStringConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.MAPS_CLIENT_API_KEY))); + final int orgId = event.getOrganizationId(); + final int eventId = event.getId(); + Map> geoInfoConfiguration = configurationManager.getStringConfigValueFrom( + Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_PROVIDER), + Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_CLIENT_API_KEY), + Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_HERE_APP_ID), + Configuration.from(orgId, eventId, ConfigurationKeys.MAPS_HERE_APP_CODE)); + + LocationDescriptor ld = LocationDescriptor.fromGeoData(event.getLatLong(), TimeZone.getTimeZone(event.getTimeZone()), geoInfoConfiguration); final boolean hasAccessPromotions = ticketCategoryRepository.countAccessRestrictedRepositoryByEventId(event.getId()) > 0 || promoCodeRepository.countByEventAndOrganizationId(event.getId(), event.getOrganizationId()) > 0; diff --git a/src/main/java/alfio/controller/api/admin/LocationApiController.java b/src/main/java/alfio/controller/api/admin/LocationApiController.java index cb8fc04a25..b177c81323 100644 --- a/src/main/java/alfio/controller/api/admin/LocationApiController.java +++ b/src/main/java/alfio/controller/api/admin/LocationApiController.java @@ -17,17 +17,19 @@ package alfio.controller.api.admin; import alfio.manager.system.ConfigurationManager; +import alfio.model.modification.support.LocationDescriptor; import alfio.model.system.Configuration; import alfio.model.system.ConfigurationKeys; import com.moodysalem.TimezoneMapper; +import lombok.AllArgsConstructor; +import lombok.Getter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import java.time.ZoneId; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import java.util.*; +import java.util.function.Function; @RestController @RequestMapping("/admin/api") @@ -46,21 +48,53 @@ public String unhandledException(Exception e) { return e.getMessage(); } - @RequestMapping(value = "/location/maps-client-api-key") - public String mapsClientApiKey() { - return configurationManager.getStringConfigValue(Configuration.getSystemConfiguration(ConfigurationKeys.MAPS_CLIENT_API_KEY)).orElse(null); - } - - @RequestMapping(value = "/location/timezones") + @RequestMapping("/location/timezones") public List getTimezones() { List s = new ArrayList<>(ZoneId.getAvailableZoneIds()); s.sort(String::compareTo); return s; } - @RequestMapping(value = "/location/timezone") + @RequestMapping("/location/timezone") public String getTimezone(@RequestParam("lat") double lat, @RequestParam("lng") double lng) { String tzId = TimezoneMapper.tzNameAt(lat, lng); return getTimezones().contains(tzId) ? tzId : null; } + + + + @RequestMapping("/location/static-map-image") + public String getMapImage( + @RequestParam("lat") String lat, + @RequestParam("lng") String lng) { + Map> geoInfoConfiguration = getGeoConf(); + return LocationDescriptor.getMapUrl(lat, lng, geoInfoConfiguration); + } + + private Map> getGeoConf() { + Function pathKeyBuilder = (key) -> Configuration.getSystemConfiguration(key); + return configurationManager.getStringConfigValueFrom( + pathKeyBuilder.apply(ConfigurationKeys.MAPS_PROVIDER), + pathKeyBuilder.apply(ConfigurationKeys.MAPS_CLIENT_API_KEY), + pathKeyBuilder.apply(ConfigurationKeys.MAPS_HERE_APP_ID), + pathKeyBuilder.apply(ConfigurationKeys.MAPS_HERE_APP_CODE)); + } + + @RequestMapping("/location/map-provider-client-api-key") + public ProviderAndKeys getGeoInfoProviderAndKeys() { + Map> geoInfoConfiguration = getGeoConf(); + ConfigurationKeys.GeoInfoProvider provider = LocationDescriptor.getProvider(geoInfoConfiguration); + Map apiKeys = new HashMap<>(); + geoInfoConfiguration.forEach((k,v) -> { + v.ifPresent(value -> apiKeys.put(k, value)); + }); + return new ProviderAndKeys(provider, apiKeys); + } + + @AllArgsConstructor + @Getter + public static class ProviderAndKeys { + private final ConfigurationKeys.GeoInfoProvider provider; + private Map keys; + } } diff --git a/src/main/java/alfio/model/modification/support/LocationDescriptor.java b/src/main/java/alfio/model/modification/support/LocationDescriptor.java index 39bd53a120..0f8e402a29 100644 --- a/src/main/java/alfio/model/modification/support/LocationDescriptor.java +++ b/src/main/java/alfio/model/modification/support/LocationDescriptor.java @@ -16,6 +16,7 @@ */ package alfio.model.modification.support; +import alfio.model.system.ConfigurationKeys; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; @@ -32,8 +33,6 @@ @EqualsAndHashCode public class LocationDescriptor { - private static final String MAP_URL = "https://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&key=${key}&zoom=16&size=400x400&markers=color:blue%7Clabel:E%7C${latitude},${longitude}"; - private static final String OPENSTREETMAP = "https://tyler-demo.herokuapp.com/?center=${latitude},${longitude}&zoom=16&size=400x400&markers=color:blue%7Clabel:E%7C${latitude},${longitude}"; private final String timeZone; private final String latitude; @@ -51,12 +50,56 @@ public LocationDescriptor(@JsonProperty("timeZone") String timeZone, this.mapUrl = mapUrl; } - public static LocationDescriptor fromGeoData(Pair coordinates, TimeZone timeZone, Optional apiKey) { + public static LocationDescriptor fromGeoData(Pair coordinates, TimeZone timeZone, Map> geoConf) { Map params = new HashMap<>(); - params.put("latitude", coordinates.getLeft()); - params.put("longitude", coordinates.getRight()); - apiKey.ifPresent((key) -> params.put("key", key)); - return new LocationDescriptor(timeZone.getID(), coordinates.getLeft(), coordinates.getRight(), new StrSubstitutor(params).replace(apiKey.isPresent() ? MAP_URL : OPENSTREETMAP)); + String lat = coordinates.getLeft(); + String lng = coordinates.getRight(); + params.put("latitude", lat); + params.put("longitude", lng); + + return new LocationDescriptor(timeZone.getID(), coordinates.getLeft(), coordinates.getRight(), getMapUrl(lat, lng, geoConf)); + } + + public static String getMapUrl(String lat, String lng, Map> geoConf) { + Map params = new HashMap<>(); + params.put("latitude", lat); + params.put("longitude", lng); + + ConfigurationKeys.GeoInfoProvider provider = getProvider(geoConf); + String mapUrl = mapUrl(provider); + + fillParams(provider, geoConf, params); + + return new StrSubstitutor(params).replace(mapUrl); + } + + // for backward compatibility reason, the logic is not straightforward + public static ConfigurationKeys.GeoInfoProvider getProvider(Map> geoConf) { + if((!geoConf.containsKey(ConfigurationKeys.MAPS_PROVIDER) || !geoConf.get(ConfigurationKeys.MAPS_PROVIDER).isPresent()) && + (geoConf.containsKey(ConfigurationKeys.MAPS_CLIENT_API_KEY) && geoConf.get(ConfigurationKeys.MAPS_CLIENT_API_KEY).isPresent())) { + return ConfigurationKeys.GeoInfoProvider.GOOGLE; + } else if (geoConf.containsKey(ConfigurationKeys.MAPS_PROVIDER) && geoConf.get(ConfigurationKeys.MAPS_PROVIDER).isPresent()) { + return geoConf.get(ConfigurationKeys.MAPS_PROVIDER).map(ConfigurationKeys.GeoInfoProvider::valueOf).orElseThrow(IllegalStateException::new); + } else { + return ConfigurationKeys.GeoInfoProvider.NONE; + } + } + + private static String mapUrl(ConfigurationKeys.GeoInfoProvider provider) { + switch (provider) { + case GOOGLE: return "https://maps.googleapis.com/maps/api/staticmap?center=${latitude},${longitude}&key=${key}&zoom=16&size=400x400&markers=color:blue%7Clabel:E%7C${latitude},${longitude}"; + case HERE: return "https://image.maps.api.here.com/mia/1.6/mapview?c=${latitude},${longitude}&z=16&w=400&h=400&poi=${latitude},${longitude}&app_id=${appId}&app_code=${appCode}"; + default: return "https://tyler-demo.herokuapp.com/?center=${latitude},${longitude}&zoom=16&size=400x400&markers=color:blue%7Clabel:E%7C${latitude},${longitude}"; + } + } + + private static void fillParams(ConfigurationKeys.GeoInfoProvider provider, Map> geoConf, Map params) { + if(ConfigurationKeys.GeoInfoProvider.GOOGLE == provider) { + geoConf.get(ConfigurationKeys.MAPS_CLIENT_API_KEY).ifPresent((key) -> params.put("key", key)); + } else if (ConfigurationKeys.GeoInfoProvider.HERE == provider) { + geoConf.get(ConfigurationKeys.MAPS_HERE_APP_ID).ifPresent((appId) -> params.put("appId", appId)); + geoConf.get(ConfigurationKeys.MAPS_HERE_APP_CODE).ifPresent((appCode) -> params.put("appCode", appCode)); + } } } diff --git a/src/main/java/alfio/model/system/ConfigurationKeys.java b/src/main/java/alfio/model/system/ConfigurationKeys.java index 3f1ad20516..eba93aa41e 100644 --- a/src/main/java/alfio/model/system/ConfigurationKeys.java +++ b/src/main/java/alfio/model/system/ConfigurationKeys.java @@ -33,7 +33,10 @@ public enum ConfigurationKeys { BASE_URL("Base application url", false, SettingCategory.GENERAL, ComponentType.TEXT, true, EnumSet.of(SYSTEM), true), - MAPS_CLIENT_API_KEY("Google maps' client api key", false, SettingCategory.GENERAL, ComponentType.TEXT, true, EnumSet.of(SYSTEM), true), + MAPS_PROVIDER("Select the maps provider (Google, Here)", false, SettingCategory.MAP, ComponentType.TEXT, false, EnumSet.of(SYSTEM), true), + MAPS_CLIENT_API_KEY("Google maps' client api key", false, SettingCategory.MAP, ComponentType.TEXT, false, EnumSet.of(SYSTEM), true), + MAPS_HERE_APP_ID("HERE map App ID", false, SettingCategory.MAP, ComponentType.TEXT, false, EnumSet.of(SYSTEM), true), + MAPS_HERE_APP_CODE("HERE map App Code", false, SettingCategory.MAP, ComponentType.TEXT, false, EnumSet.of(SYSTEM), true), RECAPTCHA_API_KEY("Recaptcha api key", false, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.of(SYSTEM), true), @@ -162,7 +165,8 @@ public enum SettingCategory { INVOICE("Invoice settings"), INVOICE_EU("Invoice settings for EU"), MAIL("E-Mail settings"), - ALFIO_PI("Offline check-in and badge printing"); + ALFIO_PI("Offline check-in and badge printing"), + MAP("Maps settings"); private final String description; SettingCategory(String description) { @@ -171,6 +175,10 @@ public enum SettingCategory { } + public enum GeoInfoProvider { + GOOGLE, HERE, NONE + } + private static final Predicate INTERNAL = ConfigurationKeys::isInternal; private final String description; private final boolean internal; diff --git a/src/main/webapp/resources/angular-templates/admin/partials/configuration/basic-settings.html b/src/main/webapp/resources/angular-templates/admin/partials/configuration/basic-settings.html index c21280e286..904f58b56b 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/configuration/basic-settings.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/configuration/basic-settings.html @@ -60,6 +60,35 @@

E-Mail

+ + +
+

+ +

+

+ +

+
+ +
+
+ +
+
+ +
+
+ + +
+

+ +

+

+ +

+
+ +
+
+ +
+
+ +
+
+