From 6c554724ab9111fb26fb1386e69b92cd5ab782d8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Fri, 5 Jul 2019 11:40:03 +0200 Subject: [PATCH 01/45] Bump version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a7d047c..c7fa1a7 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ ch.srgssr playfff - 12 + 13 jar pfff From 9da4bce56c26f6685b0304c8dc4db5947839e179 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Mon, 29 Apr 2019 01:30:24 +0200 Subject: [PATCH 02/45] Add deeplink controller to deliver the javascript play ulr parser, with tv topic and event modules parsing urls --- .../playfff/config/AuthenticationConfig.java | 2 +- .../controller/DeeplinkController.java | 128 ++++++++++++++++++ .../service/IntegrationLayerRequest.java | 13 ++ .../java/ch/srgssr/playportal/PlayTopic.java | 109 +++++++++++++++ 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java create mode 100755 src/main/java/ch/srgssr/playportal/PlayTopic.java diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index ddfda57..d2d707f 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -38,7 +38,7 @@ public AuthenticationConfig( protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() - .antMatchers("/api/v1/update/check", "/api/v1/whatisnew/text", "/api/v1/whatisnew/html", "/api/v1/version", "/api/v*/playlist/**", "/webjars/bootstrap/**", "/webjars/bootstrap/**", "/webjars/jquery/**", "/webjars/font-awesome/**").permitAll() + .antMatchers("/api/v1/update/check", "/api/v1/whatisnew/text", "/api/v1/whatisnew/html", "/api/v1/version", "/api/v*/playlist/**", "/webjars/bootstrap/**", "/webjars/bootstrap/**", "/webjars/jquery/**", "/webjars/font-awesome/**", "/deeplink/**").permitAll() .anyRequest().authenticated() .and() .formLogin() diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java new file mode 100644 index 0000000..7c07b7b --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -0,0 +1,128 @@ +package ch.srgssr.playfff.controller; + +import ch.srg.il.domain.v2_0.ModuleConfig; +import ch.srg.il.domain.v2_0.ModuleConfigList; +import ch.srgssr.playfff.model.Environment; +import ch.srgssr.playfff.service.IntegrationLayerRequest; +import ch.srgssr.playportal.PlayTopic; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +@Controller +@RequestMapping(value = "/deeplink") +public class DeeplinkController { + + private String cacheReponse; + + private RestTemplate restTemplate; + + @Autowired + private IntegrationLayerRequest integrationLayerRequest; + + public DeeplinkController(RestTemplateBuilder restTemplateBuilder) { + restTemplate = restTemplateBuilder.build(); + } + + @RequestMapping("/v1/parse_play_url.js") + @ResponseBody + public ResponseEntity parsePlayUrlJavascript() throws URISyntaxException { + URI uri = new URI("https", null, "play-mmf.herokuapp.com", 443, "/deeplink/v1/parse_play_url.js", + null, null); + + ResponseEntity jsResponseEntity = restTemplate.exchange(uri, HttpMethod.GET, null, String.class); + if (jsResponseEntity.getStatusCode() != HttpStatus.OK) { + return jsResponseEntity; + } + + String javascript = jsResponseEntity.getBody(); + + Map buMap = new HashMap(); + buMap.put("srf", "www.srf.ch"); + buMap.put("rts", "www.rts.ch"); + buMap.put("rsi", "www.rsi.ch"); + buMap.put("rtr", "www.rtr.ch"); + buMap.put("swi", "play.swissinfo.ch"); + + ObjectMapper mapperObj = new ObjectMapper(); + + // Get tv topic list + Map> tvTopicsMap = new HashMap<>(); + + for (Map.Entry bu : buMap.entrySet()) { + URI tvTopicListUri = new URI("https", null, bu.getValue(), 443, "/play/tv/topicList", + null, null); + ResponseEntity tvTopicListResponseEntity = restTemplate.exchange(tvTopicListUri, HttpMethod.GET, null, PlayTopic[].class); + if (tvTopicListResponseEntity.getBody() != null) { + PlayTopic[] tvTopicList = tvTopicListResponseEntity.getBody(); + Map tvTopicsSubMap = new HashMap<>(); + + for (PlayTopic playTopic : tvTopicList) { + tvTopicsSubMap.put(playTopic.getUrlEncodedTitle(), playTopic.getId()); + } + + tvTopicsMap.put(bu.getKey(), tvTopicsSubMap); + } + } + + String tvTopics = null; + try { + tvTopics = mapperObj.writeValueAsString(tvTopicsMap); + } catch (IOException e) { + e.printStackTrace(); + } + + if (tvTopics != null) { + javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics); + } + + // Get event module list + Map> tvEventsMap = new HashMap<>(); + + for (Map.Entry bu : buMap.entrySet()) { + ModuleConfigList moduleConfigList = integrationLayerRequest.getEvents(bu.getKey(), Environment.PROD); + if (moduleConfigList != null) { + Map tvEventsSubMap = new HashMap<>(); + + for (int i = 0; i < moduleConfigList.getModuleConfigList().size(); i++) { + ModuleConfig moduleConfig = moduleConfigList.getModuleConfigList().get(i); + tvEventsSubMap.put(moduleConfig.getSeoName(), moduleConfig.getId()); + } + + tvEventsMap.put(bu.getKey(), tvEventsSubMap); + } + } + + String tvEvents = null; + try { + tvEvents = mapperObj.writeValueAsString(tvEventsMap); + } catch (IOException e) { + e.printStackTrace(); + } + + if (tvEvents != null) { + javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents); + } + + return new ResponseEntity(javascript, HttpStatus.OK); + } +} diff --git a/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java b/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java index 7a30d73..d0640ac 100644 --- a/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java +++ b/src/main/java/ch/srgssr/playfff/service/IntegrationLayerRequest.java @@ -101,6 +101,19 @@ public MediaComposition getMediaComposition(String mediaURN, Environment environ } } + public ModuleConfigList getEvents(String bu, Environment environment) { + String path = "/integrationlayer/2.0/" + bu + "/moduleConfigList/event.json"; + try { + URI uri = new URI("http", null, environment.getBaseUrl(), PORT, path, null, null); + ResponseEntity responseEntity = restTemplate.getForEntity(uri, String.class); + SrgUnmarshaller unmarshaller = new SrgUnmarshaller(); + return unmarshaller.unmarshal(responseEntity.getBody(), MediaType.APPLICATION_JSON, ModuleConfigList.class); + } catch (Exception e) { + logger.warn("http://{}{} : {}", environment.getBaseUrl(), path, e.getMessage()); + return null; + } + } + @VisibleForTesting public RestTemplate getRestTemplate() { return restTemplate; diff --git a/src/main/java/ch/srgssr/playportal/PlayTopic.java b/src/main/java/ch/srgssr/playportal/PlayTopic.java new file mode 100755 index 0000000..1ec6e4b --- /dev/null +++ b/src/main/java/ch/srgssr/playportal/PlayTopic.java @@ -0,0 +1,109 @@ + +package ch.srgssr.playportal; + +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "id", + "title", + "urlEncodedTitle", + "url", + "latestModuleUrl", + "mostClickedModuleUrl" +}) +public class PlayTopic { + + @JsonProperty("id") + private String id; + @JsonProperty("title") + private String title; + @JsonProperty("urlEncodedTitle") + private String urlEncodedTitle; + @JsonProperty("url") + private String url; + @JsonProperty("latestModuleUrl") + private String latestModuleUrl; + @JsonProperty("mostClickedModuleUrl") + private String mostClickedModuleUrl; + @JsonIgnore + private Map additionalProperties = new HashMap(); + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("title") + public void setTitle(String title) { + this.title = title; + } + + @JsonProperty("urlEncodedTitle") + public String getUrlEncodedTitle() { + return urlEncodedTitle; + } + + @JsonProperty("urlEncodedTitle") + public void setUrlEncodedTitle(String urlEncodedTitle) { + this.urlEncodedTitle = urlEncodedTitle; + } + + @JsonProperty("url") + public String getUrl() { + return url; + } + + @JsonProperty("url") + public void setUrl(String url) { + this.url = url; + } + + @JsonProperty("latestModuleUrl") + public String getLatestModuleUrl() { + return latestModuleUrl; + } + + @JsonProperty("latestModuleUrl") + public void setLatestModuleUrl(String latestModuleUrl) { + this.latestModuleUrl = latestModuleUrl; + } + + @JsonProperty("mostClickedModuleUrl") + public String getMostClickedModuleUrl() { + return mostClickedModuleUrl; + } + + @JsonProperty("mostClickedModuleUrl") + public void setMostClickedModuleUrl(String mostClickedModuleUrl) { + this.mostClickedModuleUrl = mostClickedModuleUrl; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } + +} From 695ede652c01ac65e73df6644e307aa6c91b2759 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 5 May 2019 15:01:11 +0200 Subject: [PATCH 03/45] Cache parse play url javascript 5 minutes and update it asynchronously --- .../srgssr/playfff/config/DeeplinkConfig.java | 25 +++ .../controller/DeeplinkController.java | 111 ++--------- .../srgssr/playfff/model/DeeplinkContent.java | 25 +++ .../playfff/service/DeeplinkService.java | 177 ++++++++++++++++++ 4 files changed, 238 insertions(+), 100 deletions(-) create mode 100644 src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java create mode 100644 src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java create mode 100644 src/main/java/ch/srgssr/playfff/service/DeeplinkService.java diff --git a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java new file mode 100644 index 0000000..37293f6 --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java @@ -0,0 +1,25 @@ +package ch.srgssr.playfff.config; + +import ch.srgssr.playfff.service.DeeplinkService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * License information is available from the LICENSE file. + */ +@Configuration +@EnableScheduling +public class DeeplinkConfig { + + @Autowired + private DeeplinkService service; + + @Scheduled(fixedDelay = 5 * 60000) + public void IaaSStatusRefresh() { + service.refreshParsePlayUrlContent(); + } +} diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index 7c07b7b..0971b27 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -1,128 +1,39 @@ package ch.srgssr.playfff.controller; -import ch.srg.il.domain.v2_0.ModuleConfig; -import ch.srg.il.domain.v2_0.ModuleConfigList; -import ch.srgssr.playfff.model.Environment; -import ch.srgssr.playfff.service.IntegrationLayerRequest; -import ch.srgssr.playportal.PlayTopic; -import com.fasterxml.jackson.databind.ObjectMapper; +import ch.srgssr.playfff.model.DeeplinkContent; +import ch.srgssr.playfff.service.DeeplinkService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; /** * Copyright (c) SRG SSR. All rights reserved. - *

+ * * License information is available from the LICENSE file. */ @Controller @RequestMapping(value = "/deeplink") public class DeeplinkController { - private String cacheReponse; - - private RestTemplate restTemplate; - @Autowired - private IntegrationLayerRequest integrationLayerRequest; - - public DeeplinkController(RestTemplateBuilder restTemplateBuilder) { - restTemplate = restTemplateBuilder.build(); - } + private DeeplinkService service; @RequestMapping("/v1/parse_play_url.js") @ResponseBody public ResponseEntity parsePlayUrlJavascript() throws URISyntaxException { - URI uri = new URI("https", null, "play-mmf.herokuapp.com", 443, "/deeplink/v1/parse_play_url.js", - null, null); - - ResponseEntity jsResponseEntity = restTemplate.exchange(uri, HttpMethod.GET, null, String.class); - if (jsResponseEntity.getStatusCode() != HttpStatus.OK) { - return jsResponseEntity; - } - String javascript = jsResponseEntity.getBody(); + DeeplinkContent deeplinkContent = service.getParsePlayUrlContent(); - Map buMap = new HashMap(); - buMap.put("srf", "www.srf.ch"); - buMap.put("rts", "www.rts.ch"); - buMap.put("rsi", "www.rsi.ch"); - buMap.put("rtr", "www.rtr.ch"); - buMap.put("swi", "play.swissinfo.ch"); - - ObjectMapper mapperObj = new ObjectMapper(); - - // Get tv topic list - Map> tvTopicsMap = new HashMap<>(); - - for (Map.Entry bu : buMap.entrySet()) { - URI tvTopicListUri = new URI("https", null, bu.getValue(), 443, "/play/tv/topicList", - null, null); - ResponseEntity tvTopicListResponseEntity = restTemplate.exchange(tvTopicListUri, HttpMethod.GET, null, PlayTopic[].class); - if (tvTopicListResponseEntity.getBody() != null) { - PlayTopic[] tvTopicList = tvTopicListResponseEntity.getBody(); - Map tvTopicsSubMap = new HashMap<>(); - - for (PlayTopic playTopic : tvTopicList) { - tvTopicsSubMap.put(playTopic.getUrlEncodedTitle(), playTopic.getId()); - } - - tvTopicsMap.put(bu.getKey(), tvTopicsSubMap); - } + if (deeplinkContent != null) { + return ResponseEntity.ok() + .header("ETag", deeplinkContent.getHash()) + .body(deeplinkContent.getContent()); } - - String tvTopics = null; - try { - tvTopics = mapperObj.writeValueAsString(tvTopicsMap); - } catch (IOException e) { - e.printStackTrace(); + else { + return ResponseEntity.notFound().build(); } - - if (tvTopics != null) { - javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics); - } - - // Get event module list - Map> tvEventsMap = new HashMap<>(); - - for (Map.Entry bu : buMap.entrySet()) { - ModuleConfigList moduleConfigList = integrationLayerRequest.getEvents(bu.getKey(), Environment.PROD); - if (moduleConfigList != null) { - Map tvEventsSubMap = new HashMap<>(); - - for (int i = 0; i < moduleConfigList.getModuleConfigList().size(); i++) { - ModuleConfig moduleConfig = moduleConfigList.getModuleConfigList().get(i); - tvEventsSubMap.put(moduleConfig.getSeoName(), moduleConfig.getId()); - } - - tvEventsMap.put(bu.getKey(), tvEventsSubMap); - } - } - - String tvEvents = null; - try { - tvEvents = mapperObj.writeValueAsString(tvEventsMap); - } catch (IOException e) { - e.printStackTrace(); - } - - if (tvEvents != null) { - javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents); - } - - return new ResponseEntity(javascript, HttpStatus.OK); } } diff --git a/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java b/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java new file mode 100644 index 0000000..39c72dd --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java @@ -0,0 +1,25 @@ +package ch.srgssr.playfff.model; + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * License information is available from the LICENSE file. + */ +public class DeeplinkContent { + + private String content; + private String hash; + + public DeeplinkContent(String content, String hash) { + this.content = content; + this.hash = hash; + } + + public String getContent() { + return content; + } + + public String getHash() { + return hash; + } +} diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java new file mode 100644 index 0000000..025c16f --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java @@ -0,0 +1,177 @@ +package ch.srgssr.playfff.service; + +import ch.srg.il.domain.v2_0.ModuleConfig; +import ch.srg.il.domain.v2_0.ModuleConfigList; +import ch.srgssr.playfff.model.DeeplinkContent; +import ch.srgssr.playfff.model.Environment; +import ch.srgssr.playportal.PlayTopic; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import javax.xml.bind.DatatypeConverter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * License information is available from the LICENSE file. + */ +@Service +public class DeeplinkService { + private static final Logger logger = LoggerFactory.getLogger(DeeplinkService.class); + + private DeeplinkContent parsePlayUrlContent; + + private RestTemplate restTemplate; + + @Autowired + private IntegrationLayerRequest integrationLayerRequest; + + public DeeplinkService(RestTemplateBuilder restTemplateBuilder) { + restTemplate = restTemplateBuilder.build(); + } + + @Cacheable("DeeplinkParsePlayUrl") + public DeeplinkContent getParsePlayUrlContent() { + if (parsePlayUrlContent == null) { + refreshParsePlayUrlContent(); + } + + return parsePlayUrlContent; + } + + @CachePut("DeeplinkParsePlayUrl") + public synchronized DeeplinkContent refreshParsePlayUrlContent() { + URI uri = null; + try { + uri = new URI("https", null, "play-mmf.herokuapp.com", 443, "/deeplink/v1/parse_play_url.js", + null, null); + } catch (URISyntaxException e) { + e.printStackTrace(); + logger.warn("Couldn't get mmf original javascript: " + e.getMessage()); + } + + ResponseEntity jsResponseEntity = restTemplate.exchange(uri, HttpMethod.GET, null, String.class); + if (jsResponseEntity.getStatusCode() != HttpStatus.OK) { + return null; + } + + String javascript = jsResponseEntity.getBody(); + + Map buMap = new HashMap(); + buMap.put("srf", "www.srf.ch"); + buMap.put("rts", "www.rts.ch"); + buMap.put("rsi", "www.rsi.ch"); + buMap.put("rtr", "www.rtr.ch"); + buMap.put("swi", "play.swissinfo.ch"); + + ObjectMapper mapperObj = new ObjectMapper(); + + // Get tv topic list + Map> tvTopicsMap = new HashMap<>(); + + for (Map.Entry bu : buMap.entrySet()) { + URI tvTopicListUri = null; + try { + tvTopicListUri = new URI("https", null, bu.getValue(), 443, "/play/tv/topicList", + null, null); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + ResponseEntity tvTopicListResponseEntity = restTemplate.exchange(tvTopicListUri, HttpMethod.GET, null, PlayTopic[].class); + if (tvTopicListResponseEntity.getBody() != null) { + PlayTopic[] tvTopicList = tvTopicListResponseEntity.getBody(); + Map tvTopicsSubMap = new HashMap<>(); + + for (PlayTopic playTopic : tvTopicList) { + tvTopicsSubMap.put(playTopic.getUrlEncodedTitle(), playTopic.getId()); + } + + tvTopicsMap.put(bu.getKey(), tvTopicsSubMap); + } + } + + String tvTopics = null; + try { + tvTopics = mapperObj.writeValueAsString(tvTopicsMap); + } catch (IOException e) { + e.printStackTrace(); + } + + if (tvTopics != null) { + javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics); + } + + // Get event module list + Map> tvEventsMap = new HashMap<>(); + + for (Map.Entry bu : buMap.entrySet()) { + ModuleConfigList moduleConfigList = integrationLayerRequest.getEvents(bu.getKey(), Environment.PROD); + if (moduleConfigList != null) { + Map tvEventsSubMap = new HashMap<>(); + + for (int i = 0; i < moduleConfigList.getModuleConfigList().size(); i++) { + ModuleConfig moduleConfig = moduleConfigList.getModuleConfigList().get(i); + tvEventsSubMap.put(moduleConfig.getSeoName(), moduleConfig.getId()); + } + + tvEventsMap.put(bu.getKey(), tvEventsSubMap); + } + } + + String tvEvents = null; + try { + tvEvents = mapperObj.writeValueAsString(tvEventsMap); + } catch (IOException e) { + e.printStackTrace(); + } + + if (tvEvents != null) { + javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents); + } + + String buildHash = sha1(javascript); + Date buildDate = new Date(); + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich")); + String strDate = dateFormat.format(buildDate); + javascript = javascript.replaceAll("var parsePlayUrlBuild = \"mmf\";", "var parsePlayUrlBuild = \"" + buildHash + "\";\nvar parsePlayUrlBuildDate = \"" + strDate + "\";"); + + parsePlayUrlContent = new DeeplinkContent(javascript, buildHash); + return parsePlayUrlContent; + } + + private String sha1(String input) { + String sha1 = null; + try { + MessageDigest msdDigest = MessageDigest.getInstance("SHA-1"); + msdDigest.update(input.getBytes("UTF-8"), 0, input.length()); + sha1 = DatatypeConverter.printHexBinary(msdDigest.digest()); + } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { + logger.warn(e.getMessage()); + } + return sha1; + } +} From 369a91f762796abba191a1b3a56a48f76b87820c Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 5 May 2019 16:45:01 +0200 Subject: [PATCH 04/45] Add deeplink few tests --- .../controller/DeeplinkIntegrationTest.java | 41 +++++++++++++++++++ .../controller/DeeplinkServiceTests.java | 38 +++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java create mode 100644 src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java new file mode 100644 index 0000000..681a61b --- /dev/null +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java @@ -0,0 +1,41 @@ +package ch.srgssr.playfff.controller; + +import org.hamcrest.core.IsNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class DeeplinkIntegrationTest { + private MockMvc mvc; + + @Autowired + private WebApplicationContext context; + + @Before + public void setup() { + mvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + public void getParsePlayUrl() throws Exception { + mvc.perform(get("/deeplink/v1/parse_play_url.js")) + .andExpect(status().isOk()) + .andExpect(header().string("ETag", IsNull.notNullValue())) + .andExpect(content().string(IsNull.notNullValue())); + } +} diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java new file mode 100644 index 0000000..85dc1ec --- /dev/null +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java @@ -0,0 +1,38 @@ +package ch.srgssr.playfff.controller; + +import ch.srgssr.playfff.model.DeeplinkContent; +import ch.srgssr.playfff.service.DeeplinkService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class DeeplinkServiceTests { + + @Autowired + private DeeplinkService deeplinkService; + + @Test + public void getParsePlayUrlContentTest() { + DeeplinkContent deeplinkContent = deeplinkService.getParsePlayUrlContent(); + + Assert.assertNotNull(deeplinkContent.getContent()); + Assert.assertNotNull(deeplinkContent.getHash()); + Assert.assertTrue(deeplinkContent.getContent().contains(deeplinkContent.getHash())); + } + + @Test + public void refreshParsePlayUrlContentTest() { + DeeplinkContent deeplinkContent = deeplinkService.refreshParsePlayUrlContent(); + + Assert.assertNotNull(deeplinkContent.getContent()); + Assert.assertNotNull(deeplinkContent.getHash()); + Assert.assertTrue(deeplinkContent.getContent().contains(deeplinkContent.getHash())); + Assert.assertFalse(deeplinkContent.getContent().contains("INJECT TVTOPICS OBJECT")); + Assert.assertFalse(deeplinkContent.getContent().contains("INJECT TVEVENTS OBJECT")); + } +} From 7a4a0f2ff6ad78e73eb7a0d17eec3c6097d613c8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 5 May 2019 17:42:33 +0200 Subject: [PATCH 05/45] Implement ETag and If-None-Match --- .../playfff/controller/DeeplinkController.java | 17 +++++++++++------ .../controller/DeeplinkIntegrationTest.java | 10 ++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index 0971b27..c7d2641 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -3,8 +3,10 @@ import ch.srgssr.playfff.model.DeeplinkContent; import ch.srgssr.playfff.service.DeeplinkService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.net.URISyntaxException; @@ -23,16 +25,19 @@ public class DeeplinkController { @RequestMapping("/v1/parse_play_url.js") @ResponseBody - public ResponseEntity parsePlayUrlJavascript() throws URISyntaxException { + public ResponseEntity parsePlayUrlJavascript(@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatchHeader) throws URISyntaxException { DeeplinkContent deeplinkContent = service.getParsePlayUrlContent(); if (deeplinkContent != null) { - return ResponseEntity.ok() - .header("ETag", deeplinkContent.getHash()) - .body(deeplinkContent.getContent()); - } - else { + if (ifNoneMatchHeader != null && ifNoneMatchHeader.equals(deeplinkContent.getHash())) { + return new ResponseEntity<>(HttpStatus.NOT_MODIFIED); + } else { + return ResponseEntity.ok() + .header("ETag", deeplinkContent.getHash()) + .body(deeplinkContent.getContent()); + } + } else { return ResponseEntity.notFound().build(); } } diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java index 681a61b..2ebe8d7 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; @@ -33,9 +34,14 @@ public void setup() { @Test public void getParsePlayUrl() throws Exception { - mvc.perform(get("/deeplink/v1/parse_play_url.js")) + MvcResult mvcResult = mvc.perform(get("/deeplink/v1/parse_play_url.js")) .andExpect(status().isOk()) .andExpect(header().string("ETag", IsNull.notNullValue())) - .andExpect(content().string(IsNull.notNullValue())); + .andExpect(content().string(IsNull.notNullValue())).andReturn(); + String eTag = mvcResult.getResponse().getHeader("ETag"); + + mvc.perform(get("/deeplink/v1/parse_play_url.js").header("If-None-Match", eTag)) + .andExpect(status().isNotModified()) + .andExpect(content().string("")); } } From 1ec9471737d4bf832859eb5c61e86057d1fb11a2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 11 May 2019 14:20:56 +0200 Subject: [PATCH 06/45] Use Integration layer date format --- src/main/java/ch/srgssr/playfff/service/DeeplinkService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java index 025c16f..fd5f1a3 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java @@ -154,7 +154,7 @@ public synchronized DeeplinkContent refreshParsePlayUrlContent() { String buildHash = sha1(javascript); Date buildDate = new Date(); - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich")); String strDate = dateFormat.format(buildDate); javascript = javascript.replaceAll("var parsePlayUrlBuild = \"mmf\";", "var parsePlayUrlBuild = \"" + buildHash + "\";\nvar parsePlayUrlBuildDate = \"" + strDate + "\";"); From ed13d76c477b41713a0ef13b8a96bdfb57ac6d9c Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 11 May 2019 15:38:22 +0200 Subject: [PATCH 07/45] Fix cache headers for parse_play_url.js --- .../playfff/config/AuthenticationConfig.java | 8 ++++++++ .../controller/DeeplinkController.java | 19 +++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index d2d707f..f42d6d1 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; @@ -51,6 +52,13 @@ protected void configure(HttpSecurity http) throws Exception { .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } + @Override + public void configure(WebSecurity web) throws Exception { + web + .ignoring() + .antMatchers("/deeplink/v1/parse_play_url.js"); + } + @Bean @Override public UserDetailsService userDetailsService() { diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index c7d2641..973b201 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -3,13 +3,11 @@ import ch.srgssr.playfff.model.DeeplinkContent; import ch.srgssr.playfff.service.DeeplinkService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; +import org.springframework.http.CacheControl; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; -import java.net.URISyntaxException; /** * Copyright (c) SRG SSR. All rights reserved. @@ -23,20 +21,17 @@ public class DeeplinkController { @Autowired private DeeplinkService service; - @RequestMapping("/v1/parse_play_url.js") + @RequestMapping(value="/v1/parse_play_url.js") @ResponseBody - public ResponseEntity parsePlayUrlJavascript(@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatchHeader) throws URISyntaxException { + public ResponseEntity parsePlayUrlJavascript() { DeeplinkContent deeplinkContent = service.getParsePlayUrlContent(); if (deeplinkContent != null) { - if (ifNoneMatchHeader != null && ifNoneMatchHeader.equals(deeplinkContent.getHash())) { - return new ResponseEntity<>(HttpStatus.NOT_MODIFIED); - } else { - return ResponseEntity.ok() - .header("ETag", deeplinkContent.getHash()) - .body(deeplinkContent.getContent()); - } + return ResponseEntity.ok() + .cacheControl(CacheControl.empty().cachePublic()) + .eTag(deeplinkContent.getHash()) + .body(deeplinkContent.getContent()); } else { return ResponseEntity.notFound().build(); } From cb5c5d51a8b9f4981e92295ed4585157500cfc6a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 12 May 2019 19:02:17 +0200 Subject: [PATCH 08/45] Add parsing report API, rename javascript route API --- .../playfff/config/AuthenticationConfig.java | 6 ++-- .../controller/DeeplinkController.java | 29 ++++++++++++--- .../srgssr/playfff/model/ParsingReport.java | 35 +++++++++++++++++++ .../repository/ParsingReportRepository.java | 16 +++++++++ .../playfff/service/ParsingReportService.java | 27 ++++++++++++++ 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ch/srgssr/playfff/model/ParsingReport.java create mode 100644 src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java create mode 100644 src/main/java/ch/srgssr/playfff/service/ParsingReportService.java diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index f42d6d1..141c4c7 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -39,7 +40,7 @@ public AuthenticationConfig( protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() - .antMatchers("/api/v1/update/check", "/api/v1/whatisnew/text", "/api/v1/whatisnew/html", "/api/v1/version", "/api/v*/playlist/**", "/webjars/bootstrap/**", "/webjars/bootstrap/**", "/webjars/jquery/**", "/webjars/font-awesome/**", "/deeplink/**").permitAll() + .antMatchers("/api/v1/update/check", "/api/v1/whatisnew/text", "/api/v1/whatisnew/html", "/api/v1/version", "/api/v*/playlist/**", "/webjars/bootstrap/**", "/webjars/bootstrap/**", "/webjars/jquery/**", "/webjars/font-awesome/**").permitAll() .anyRequest().authenticated() .and() .formLogin() @@ -56,7 +57,8 @@ protected void configure(HttpSecurity http) throws Exception { public void configure(WebSecurity web) throws Exception { web .ignoring() - .antMatchers("/deeplink/v1/parse_play_url.js"); + .antMatchers("/api/v1/deeplink/parse_play_url.js") + .antMatchers(HttpMethod.POST, "/api/v1/deeplink/report"); } @Bean diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index 973b201..0b8b3d6 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -1,13 +1,18 @@ package ch.srgssr.playfff.controller; import ch.srgssr.playfff.model.DeeplinkContent; +import ch.srgssr.playfff.model.ParsingReport; +import ch.srgssr.playfff.model.Update; import ch.srgssr.playfff.service.DeeplinkService; +import ch.srgssr.playfff.service.ParsingReportService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.WebRequest; /** * Copyright (c) SRG SSR. All rights reserved. @@ -15,13 +20,16 @@ * License information is available from the LICENSE file. */ @Controller -@RequestMapping(value = "/deeplink") +@CrossOrigin(origins = "*") public class DeeplinkController { @Autowired private DeeplinkService service; - @RequestMapping(value="/v1/parse_play_url.js") + @Autowired + private ParsingReportService parsingReportService; + + @RequestMapping(value="/api/v1/deeplink/parse_play_url.js") @ResponseBody public ResponseEntity parsePlayUrlJavascript() { @@ -36,4 +44,17 @@ public ResponseEntity parsePlayUrlJavascript() { return ResponseEntity.notFound().build(); } } + + @PostMapping("/api/v1/deeplink/report") + public ResponseEntity create(@RequestBody ParsingReport parsingReport, WebRequest webRequest) { + + if (parsingReport == null + || parsingReport.environment == null + || parsingReport.clientTime == null || parsingReport.clientId == null || parsingReport.clientVersion == null + || parsingReport.jsVersion == null || parsingReport.jsBuild == null || parsingReport.url == null) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } else { + return new ResponseEntity<>(parsingReportService.create(parsingReport), HttpStatus.CREATED); + } + } } diff --git a/src/main/java/ch/srgssr/playfff/model/ParsingReport.java b/src/main/java/ch/srgssr/playfff/model/ParsingReport.java new file mode 100644 index 0000000..d558a99 --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/model/ParsingReport.java @@ -0,0 +1,35 @@ +package ch.srgssr.playfff.model; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import javax.persistence.*; +import java.util.Date; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +@Entity +@Table(name = "parsing_reports") +public class ParsingReport { + private static final long serialVersionUID = -3009157732242241606L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + @Temporal(TemporalType.TIMESTAMP) + @JsonFormat(shape = JsonFormat.Shape.STRING, locale = "en_US_POSIX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "Europe/Zurich") + public Date clientTime; + public String clientId; + public String clientVersion; + + public String environment; + + public String jsVersion; + public String jsBuild; + + @Column(length = 512) + public String url; +} diff --git a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java new file mode 100644 index 0000000..f668a03 --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java @@ -0,0 +1,16 @@ +package ch.srgssr.playfff.repository; + +import ch.srgssr.playfff.model.ParsingReport; +import ch.srgssr.playfff.model.Update; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +public interface ParsingReportRepository extends CrudRepository { + List findAllByOrderByIdDesc(); +} diff --git a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java new file mode 100644 index 0000000..f07036f --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java @@ -0,0 +1,27 @@ +package ch.srgssr.playfff.service; + +import ch.srgssr.playfff.model.ParsingReport; +import ch.srgssr.playfff.model.Update; +import ch.srgssr.playfff.repository.ParsingReportRepository; +import ch.srgssr.playfff.repository.UpdateRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import javax.transaction.Transactional; +import java.util.List; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +@Repository +public class ParsingReportService { + @Autowired + private ParsingReportRepository repository; + + @Transactional + public ParsingReport create(ParsingReport parsingReport) { + return repository.save(parsingReport); + } +} From bc081462a187b51c5bb9e5a3e9ac5315feb84823 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 12 May 2019 19:26:30 +0200 Subject: [PATCH 09/45] Store only 1 version of an url/jsVersion/clientId --- .../controller/DeeplinkController.java | 7 +++--- .../srgssr/playfff/model/ParsingReport.java | 6 ++--- .../repository/ParsingReportRepository.java | 3 ++- .../playfff/service/ParsingReportService.java | 22 +++++++++++++++++-- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index 0b8b3d6..b0916ba 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -49,12 +49,11 @@ public ResponseEntity parsePlayUrlJavascript() { public ResponseEntity create(@RequestBody ParsingReport parsingReport, WebRequest webRequest) { if (parsingReport == null - || parsingReport.environment == null - || parsingReport.clientTime == null || parsingReport.clientId == null || parsingReport.clientVersion == null - || parsingReport.jsVersion == null || parsingReport.jsBuild == null || parsingReport.url == null) { + || parsingReport.clientTime == null || parsingReport.clientId == null + || parsingReport.jsVersion == null || parsingReport.url == null) { return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); } else { - return new ResponseEntity<>(parsingReportService.create(parsingReport), HttpStatus.CREATED); + return new ResponseEntity<>(parsingReportService.save(parsingReport), HttpStatus.CREATED); } } } diff --git a/src/main/java/ch/srgssr/playfff/model/ParsingReport.java b/src/main/java/ch/srgssr/playfff/model/ParsingReport.java index d558a99..65b5cc2 100644 --- a/src/main/java/ch/srgssr/playfff/model/ParsingReport.java +++ b/src/main/java/ch/srgssr/playfff/model/ParsingReport.java @@ -23,13 +23,11 @@ public class ParsingReport { @JsonFormat(shape = JsonFormat.Shape.STRING, locale = "en_US_POSIX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone = "Europe/Zurich") public Date clientTime; public String clientId; - public String clientVersion; - - public String environment; public String jsVersion; - public String jsBuild; @Column(length = 512) public String url; + + public int count; } diff --git a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java index f668a03..ef423e6 100644 --- a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java +++ b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java @@ -1,7 +1,6 @@ package ch.srgssr.playfff.repository; import ch.srgssr.playfff.model.ParsingReport; -import ch.srgssr.playfff.model.Update; import org.springframework.data.repository.CrudRepository; import java.util.List; @@ -12,5 +11,7 @@ * License information is available from the LICENSE file. */ public interface ParsingReportRepository extends CrudRepository { + List findByClientIdAndJsVersionAndUrl(String clientId, String jsVersion, String url); + List findAllByOrderByIdDesc(); } diff --git a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java index f07036f..08fa0b2 100644 --- a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java +++ b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java @@ -21,7 +21,25 @@ public class ParsingReportService { private ParsingReportRepository repository; @Transactional - public ParsingReport create(ParsingReport parsingReport) { - return repository.save(parsingReport); + public ParsingReport save(ParsingReport parsingReport) { + ParsingReport currentParsingReport = getParsingReport(parsingReport.clientId, parsingReport.jsVersion, parsingReport.url); + if (currentParsingReport != null) { + currentParsingReport.count += 1; + currentParsingReport.clientTime = parsingReport.clientTime; + return repository.save(currentParsingReport); + } else { + parsingReport.count = 1; + return repository.save(parsingReport); + } + } + + private ParsingReport getParsingReport(String clientId, String jsVersion, String url) { + List parsingReports = repository.findByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); + + if (parsingReports.isEmpty()) { + return null; + } else { + return parsingReports.get(0); + } } } From 06dc7053e6a4a0f0d81fe510cd10bbc0a10b659d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 12 May 2019 21:04:54 +0200 Subject: [PATCH 10/45] Fix code --- .../java/ch/srgssr/playfff/config/AuthenticationConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index 141c4c7..8650d31 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -32,7 +32,7 @@ public AuthenticationConfig( @Value("${PFFF_USER:}") String user, @Value("${PFFF_PASSWORD:}") String password) { - this.user = user;; + this.user = user; this.password = password; } From 2cc6ebe2ad6a6b400e2e5604b964fc88d031bb51 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 12 May 2019 21:14:11 +0200 Subject: [PATCH 11/45] Keep only latest 2500 parsing reports --- .../srgssr/playfff/config/DeeplinkConfig.java | 13 ++++++--- .../repository/ParsingReportRepository.java | 7 +++-- .../playfff/service/ParsingReportService.java | 28 ++++++++++++++----- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java index 37293f6..65beb8e 100644 --- a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java @@ -1,6 +1,7 @@ package ch.srgssr.playfff.config; import ch.srgssr.playfff.service.DeeplinkService; +import ch.srgssr.playfff.service.ParsingReportService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @@ -16,10 +17,14 @@ public class DeeplinkConfig { @Autowired - private DeeplinkService service; + private DeeplinkService deeplinkService; - @Scheduled(fixedDelay = 5 * 60000) - public void IaaSStatusRefresh() { - service.refreshParsePlayUrlContent(); + @Autowired + private ParsingReportService parsingReportService; + + @Scheduled(fixedDelayString = "${DEEPLINK_REFRESH_DELAY_MS:300000}") + public void DeeplinkRefresh() { + deeplinkService.refreshParsePlayUrlContent(); + parsingReportService.purgeOlderReports(); } } diff --git a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java index ef423e6..31a0a12 100644 --- a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java +++ b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java @@ -3,6 +3,7 @@ import ch.srgssr.playfff.model.ParsingReport; import org.springframework.data.repository.CrudRepository; +import java.util.Date; import java.util.List; /** @@ -11,7 +12,9 @@ * License information is available from the LICENSE file. */ public interface ParsingReportRepository extends CrudRepository { - List findByClientIdAndJsVersionAndUrl(String clientId, String jsVersion, String url); + ParsingReport findFirstByClientIdAndJsVersionAndUrl(String clientId, String jsVersion, String url); - List findAllByOrderByIdDesc(); + List findAllByOrderByClientTimeDesc(); + + List findAllByClientTimeLessThan(Date date); } diff --git a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java index 08fa0b2..b495c82 100644 --- a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java +++ b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java @@ -5,6 +5,7 @@ import ch.srgssr.playfff.repository.ParsingReportRepository; import ch.srgssr.playfff.repository.UpdateRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Repository; import javax.transaction.Transactional; @@ -20,6 +21,14 @@ public class ParsingReportService { @Autowired private ParsingReportRepository repository; + private int maxParsingReports; + + public ParsingReportService( + @Value("${MAX_PARSING_REPORTS:2500}") String maxParsingReportsString) { + + this.maxParsingReports = Integer.valueOf(maxParsingReportsString); + } + @Transactional public ParsingReport save(ParsingReport parsingReport) { ParsingReport currentParsingReport = getParsingReport(parsingReport.clientId, parsingReport.jsVersion, parsingReport.url); @@ -33,13 +42,18 @@ public ParsingReport save(ParsingReport parsingReport) { } } - private ParsingReport getParsingReport(String clientId, String jsVersion, String url) { - List parsingReports = repository.findByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); - - if (parsingReports.isEmpty()) { - return null; - } else { - return parsingReports.get(0); + @Transactional + public synchronized void purgeOlderReports() { + // Keep only latest reports + if (repository.count() > maxParsingReports) { + List allReports = repository.findAllByOrderByClientTimeDesc(); + ParsingReport report = allReports.get(maxParsingReports - 1); + List olderReports = repository.findAllByClientTimeLessThan(report.clientTime); + repository.delete(olderReports); } } + + private ParsingReport getParsingReport(String clientId, String jsVersion, String url) { + return repository.findFirstByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); + } } From bf18722868a3c16f0c6ed9a5436527c2912dd65d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sun, 7 Jul 2019 17:51:14 +0200 Subject: [PATCH 12/45] Fix unit tests --- .../ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java index 2ebe8d7..b5bd420 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java @@ -34,13 +34,13 @@ public void setup() { @Test public void getParsePlayUrl() throws Exception { - MvcResult mvcResult = mvc.perform(get("/deeplink/v1/parse_play_url.js")) + MvcResult mvcResult = mvc.perform(get("/api/v1/deeplink/parse_play_url.js")) .andExpect(status().isOk()) .andExpect(header().string("ETag", IsNull.notNullValue())) .andExpect(content().string(IsNull.notNullValue())).andReturn(); String eTag = mvcResult.getResponse().getHeader("ETag"); - mvc.perform(get("/deeplink/v1/parse_play_url.js").header("If-None-Match", eTag)) + mvc.perform(get("/api/v1/deeplink/parse_play_url.js").header("If-None-Match", eTag)) .andExpect(status().isNotModified()) .andExpect(content().string("")); } From 0787418420b357811b872e0a46e0f373d145a435 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Thu, 18 Jul 2019 18:10:15 +0200 Subject: [PATCH 13/45] Add semikolon in js --- src/main/java/ch/srgssr/playfff/service/DeeplinkService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java index fd5f1a3..e760779 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java @@ -120,7 +120,7 @@ public synchronized DeeplinkContent refreshParsePlayUrlContent() { } if (tvTopics != null) { - javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics); + javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics + ";"); } // Get event module list @@ -148,7 +148,7 @@ public synchronized DeeplinkContent refreshParsePlayUrlContent() { } if (tvEvents != null) { - javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents); + javascript = javascript.replaceAll("\\/\\* INJECT TVEVENTS OBJECT \\*\\/", "var tvEvents = " + tvEvents + ";"); } String buildHash = sha1(javascript); From 22fb1263f0e494fdc0a58b262d135fdaa7e8e38a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Fri, 19 Jul 2019 21:52:08 +0200 Subject: [PATCH 14/45] Move package --- .../ch/srgssr/{ => playfff/model}/playportal/PlayTopic.java | 2 +- src/main/java/ch/srgssr/playfff/service/DeeplinkService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/main/java/ch/srgssr/{ => playfff/model}/playportal/PlayTopic.java (98%) diff --git a/src/main/java/ch/srgssr/playportal/PlayTopic.java b/src/main/java/ch/srgssr/playfff/model/playportal/PlayTopic.java similarity index 98% rename from src/main/java/ch/srgssr/playportal/PlayTopic.java rename to src/main/java/ch/srgssr/playfff/model/playportal/PlayTopic.java index 1ec6e4b..6ee6007 100755 --- a/src/main/java/ch/srgssr/playportal/PlayTopic.java +++ b/src/main/java/ch/srgssr/playfff/model/playportal/PlayTopic.java @@ -1,5 +1,5 @@ -package ch.srgssr.playportal; +package ch.srgssr.playfff.model.playportal; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java index e760779..a8cf5f5 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java @@ -4,7 +4,7 @@ import ch.srg.il.domain.v2_0.ModuleConfigList; import ch.srgssr.playfff.model.DeeplinkContent; import ch.srgssr.playfff.model.Environment; -import ch.srgssr.playportal.PlayTopic; +import ch.srgssr.playfff.model.playportal.PlayTopic; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From e4f847e55db61ed2a75ef5acdf968a24f763c244 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Fri, 19 Jul 2019 22:09:03 +0200 Subject: [PATCH 15/45] Fix typo --- .../java/ch/srgssr/playfff/controller/DeeplinkController.java | 2 +- src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java | 2 +- src/main/java/ch/srgssr/playfff/service/DeeplinkService.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index b0916ba..19442b4 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -16,7 +16,7 @@ /** * Copyright (c) SRG SSR. All rights reserved. - * + *

* License information is available from the LICENSE file. */ @Controller diff --git a/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java b/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java index 39c72dd..04e35a6 100644 --- a/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java +++ b/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java @@ -2,7 +2,7 @@ /** * Copyright (c) SRG SSR. All rights reserved. - * + *

* License information is available from the LICENSE file. */ public class DeeplinkContent { diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java index a8cf5f5..9d3844f 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java @@ -34,7 +34,7 @@ /** * Copyright (c) SRG SSR. All rights reserved. - * + *

* License information is available from the LICENSE file. */ @Service From 96cad262e72292b28fc1e346ae734ec328331837 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Fri, 19 Jul 2019 23:04:04 +0200 Subject: [PATCH 16/45] Use local parse_play_url.js --- .../playfff/helper/BaseResourceString.java | 28 + .../playfff/service/DeeplinkService.java | 21 +- src/main/resources/parse_play_url.js | 705 ++++++++++++++++++ 3 files changed, 739 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java create mode 100644 src/main/resources/parse_play_url.js diff --git a/src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java b/src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java new file mode 100644 index 0000000..ae7fc10 --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/helper/BaseResourceString.java @@ -0,0 +1,28 @@ +package ch.srgssr.playfff.helper; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +public class BaseResourceString { + public static String getString(ApplicationContext applicationContext, String name) { + try { + Resource resource = applicationContext.getResource("classpath:" + name); + if (resource == null) { + throw new RuntimeException("No resource: " + name); + } + String s = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); + return s; + } catch (IOException e) { + throw new RuntimeException("IO Exception for " + name, e); + } + } +} diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java index 9d3844f..324cec2 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java @@ -2,6 +2,7 @@ import ch.srg.il.domain.v2_0.ModuleConfig; import ch.srg.il.domain.v2_0.ModuleConfigList; +import ch.srgssr.playfff.helper.BaseResourceString; import ch.srgssr.playfff.model.DeeplinkContent; import ch.srgssr.playfff.model.Environment; import ch.srgssr.playfff.model.playportal.PlayTopic; @@ -12,6 +13,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationContext; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -45,6 +47,9 @@ public class DeeplinkService { private RestTemplate restTemplate; + @Autowired + protected ApplicationContext applicationContext; + @Autowired private IntegrationLayerRequest integrationLayerRequest; @@ -63,21 +68,7 @@ public DeeplinkContent getParsePlayUrlContent() { @CachePut("DeeplinkParsePlayUrl") public synchronized DeeplinkContent refreshParsePlayUrlContent() { - URI uri = null; - try { - uri = new URI("https", null, "play-mmf.herokuapp.com", 443, "/deeplink/v1/parse_play_url.js", - null, null); - } catch (URISyntaxException e) { - e.printStackTrace(); - logger.warn("Couldn't get mmf original javascript: " + e.getMessage()); - } - - ResponseEntity jsResponseEntity = restTemplate.exchange(uri, HttpMethod.GET, null, String.class); - if (jsResponseEntity.getStatusCode() != HttpStatus.OK) { - return null; - } - - String javascript = jsResponseEntity.getBody(); + String javascript = BaseResourceString.getString(applicationContext, "parse_play_url.js"); Map buMap = new HashMap(); buMap.put("srf", "www.srf.ch"); diff --git a/src/main/resources/parse_play_url.js b/src/main/resources/parse_play_url.js new file mode 100644 index 0000000..a29f105 --- /dev/null +++ b/src/main/resources/parse_play_url.js @@ -0,0 +1,705 @@ +// parse_play_url + +var parsePlayUrlVersion = "v1.0-dev14"; +var parsePlayUrlBuild = "mmf"; + +var parsePlayUrl = function(urlString) { + var url = urlString; + try { + url = new URL(urlString); + } + catch(error) { + console.log("Can't read URL: " + error); + return null; + } + + var queryParams = {}; + for (let queryItem of url.searchParams) { + queryParams[queryItem[0]] = queryItem[1]; + } + + return parseForPlayApp(url.hostname, url.pathname, queryParams, url.hash); +} + + +var parseForPlayApp = function(hostname, pathname, queryParams, anchor) { + + // fix path issue + pathname = pathname.replace("//", "/"); + + // Get BU + var bu = null; + switch (true) { + case hostname.endsWith("tp.srgssr.ch") || hostname.endsWith("player.rts.ch") || hostname.endsWith("player.rsi.ch") || hostname.endsWith("player.rtr.ch") || hostname.endsWith("player.swissinfo.ch") || hostname.endsWith("player.srf.ch"): + bu = "tp"; + break; + case hostname.includes("rts.ch") || hostname.includes("srgplayer-rts") || (hostname.includes("play-mmf") && pathname.startsWith("/rts/")): + bu = "rts"; + break; + case hostname.includes("rsi.ch") || hostname.includes("srgplayer-rsi") || (hostname.includes("play-mmf") && pathname.startsWith("/rsi/")): + bu = "rsi"; + break; + case hostname.includes("rtr.ch") || hostname.includes("srgplayer-rtr") || (hostname.includes("play-mmf") && pathname.startsWith("/rtr/")): + bu = "rtr"; + break; + case hostname.includes("swissinfo.ch") || hostname.includes("srgplayer-swi") || (hostname.includes("play-mmf") && pathname.startsWith("/swi/")): + bu = "swi"; + break; + case hostname.includes("srf.ch") || hostname.includes("srgplayer-srf") || (hostname.includes("play-mmf") && pathname.startsWith("/srf/")): + bu = "srf"; + break; + case hostname.includes("play-mmf") && pathname.startsWith("/mmf/"): + bu = "mmf"; + break; + } + + if (! bu) { + console.log("This hostname URL is not part of Play SRG URLs."); + return null; + } + + // Get server + var server = serverForUrl(hostname, pathname, queryParams); + + /** + * Catch special case: Player web + * + * Ex: https://tp.srgssr.ch/p/rts/default?urn=urn:rts:video:6735513 + * Ex: https://player.rts.ch/p/rts/default?urn=urn:rts:video:6735513&start=60 + */ + if (bu == "tp") { + if (pathname.startsWith("/p/")) { + var mediaURN = queryParams["urn"]; + if (mediaURN) { + var redirectBu = "tp"; + switch (true) { + case pathname.startsWith("/p/srf/"): + redirectBu = "srf"; + break; + case pathname.startsWith("/p/rts/"): + redirectBu = "rts"; + break; + case pathname.startsWith("/p/rsi/"): + redirectBu = "rsi"; + break; + case pathname.startsWith("/p/rtr/"): + redirectBu = "rtr"; + break; + case pathname.startsWith("/p/swi/"): + redirectBu = "swi"; + break; + } + var startTime = queryParams["start"]; + return openMediaURN(server, redirectBu, mediaURN, startTime); + } + } + } + /** + * Catch special case: Play MMF + * + * Ex: https://play-mmf.herokuapp.com/mmf/ + * Ex: https://play-mmf.herokuapp.com/mmf/media/urn:rts:video:_rts_info_delay + */ + if (bu == "mmf") { + if (pathname.includes("/media/")) { + var lastPathComponent = pathname.split("/").slice(-1)[0]; + return openMediaURN(server, bu, lastPathComponent, null); + } + + // Returns default TV homepage + return openPage(server, bu, "tv:home", null, null); + } + + if (hostname.includes("play-mmf") && ! pathname.startsWith("/mmf/")) { + pathname = pathname.substring(4); + } + + /** + * Catch special case: shared RTS media urls built by RTS MAM. + * + * Ex: https://www.rts.ch/video + * Ex: https://www.rts.ch/video/emissions/signes/9901229-la-route-de-lexil-en-langue-des-signes.html + * Ex: https://www.rts.ch/audio/la-1ere + */ + if (bu == "rts" && (pathname.startsWith("/video") || pathname.startsWith("/audio"))) { + var mediaId = null; + + if (pathname.endsWith(".html")) { + var lastPath = pathname.substr(pathname.lastIndexOf('/') + 1); + mediaId = lastPath.split('.')[0].split('-')[0]; + } + + if (mediaId) { + var mediaType = (pathname.startsWith("/video")) ? "video" : "audio"; + return openMedia(server, bu, mediaType, mediaId, null); + } + else if (pathname.startsWith("/video")) { + // Returns default TV homepage + return openPage(server, bu, "tv:home", null, null); + } + else { + var channelId = null; + var paths = pathname.split('/'); + if (paths.length > 2) { + var radioId = paths[2]; + switch (radioId) { + case "la-1ere": + channelId = "a9e7621504c6959e35c3ecbe7f6bed0446cdf8da"; + break; + case "espace-2": + channelId = "a83f29dee7a5d0d3f9fccdb9c92161b1afb512db"; + break; + case "couleur3": + channelId = "8ceb28d9b3f1dd876d1df1780f908578cbefc3d7"; + break; + case "option-musique": + channelId = "f8517e5319a515e013551eea15aa114fa5cfbc3a"; + break; + } + } + // Returns default radio homepage + return openPage(server, bu, "radio:home", channelId, null); + } + } + + + if (! pathname.startsWith("/play")) { + console.log("No /play path in url."); + return null; + } + + /** + * Catch classic media urls + * + * Ex: https://www.rts.ch/play/tv/faut-pas-croire/video/exportations-darmes--la-suisse-vend-t-elle-la-guerre-ou-la-paix-?id=9938530 + */ + var mediaType = null; + switch (true) { + case pathname.includes("/video/"): + mediaType = "video"; + break; + case pathname.includes("/audio/"): + mediaType = "audio"; + break; + } + + if (mediaType) { + var mediaId = queryParams["id"]; + if (mediaId) { + var startTime = queryParams["startTime"]; + return openMedia(server, bu, mediaType, mediaId, startTime); + } + else { + mediaType = null; + } + } + + /** + * Catch redirect media urls + * + * Ex: https://www.rts.ch/play/tv/redirect/detail/9938530 + */ + switch (true) { + case pathname.includes("/tv/redirect/detail/"): + mediaType = "video"; + break; + case pathname.includes("/radio/redirect/detail/"): + mediaType = "audio"; + break; + } + + if (mediaType) { + var mediaId = pathname.substr(pathname.lastIndexOf('/') + 1); + if (mediaId) { + var startTime = queryParams["startTime"]; + return openMedia(server, bu, mediaType, mediaId, startTime); + } + else { + mediaType = null; + } + } + + /** + * Catch live TV urls + * + * Ex: https://www.srf.ch/play/tv/live?tvLiveId=c49c1d73-2f70-0001-138a-15e0c4ccd3d0 + */ + if (pathname.endsWith("/tv/live") || pathname.endsWith("/tv/direct")) { + var mediaId = queryParams["tvLiveId"]; + if (mediaId) { + return openMedia(server, bu, "video", mediaId, null); + } + else { + // Returns default TV homepage + return openPage(server, bu, "tv:home", null, null); + } + } + + /** + * Catch live radio urls + * + * Ex: https://www.rsi.ch/play/radio/livepopup/rete-uno + */ + if (pathname.includes("/radio/livepopup/")) { + var mediaBu = null; + var mediaId = null; + switch (pathname.substr(pathname.lastIndexOf('/') + 1)) { + case "radio-srf-1": + mediaBu = "srf"; + mediaId = "69e8ac16-4327-4af4-b873-fd5cd6e895a7"; + break; + case "radio-srf-2-kultur": + mediaBu = "srf"; + mediaId = "c8537421-c9c5-4461-9c9c-c15816458b46"; + break; + case "radio-srf-3": + mediaBu = "srf"; + mediaId = "dd0fa1ba-4ff6-4e1a-ab74-d7e49057d96f"; + break; + case "radio-srf-4-news": + mediaBu = "srf"; + mediaId = "ee1fb348-2b6a-4958-9aac-ec6c87e190da"; + break; + case "radio-srf-musikwelle": + mediaBu = "srf"; + mediaId = "a9c5c070-8899-46c7-ac27-f04f1be902fd"; + break; + case "radio-srf-virus": + mediaBu = "srf"; + mediaId = "66815fe2-9008-4853-80a5-f9caaffdf3a9"; + break; + case "la-1ere": + mediaBu = "rts"; + mediaId = "3262320"; + break; + case "espace-2": + mediaBu = "rts"; + mediaId = "3262362"; + break; + case "couleur-3": + mediaBu = "rts"; + mediaId = "3262363"; + break; + case "option-musique": + mediaBu = "rts"; + mediaId = "3262364"; + break; + case "rete-uno": + mediaBu = "rsi"; + mediaId = "livestream_ReteUno"; + break; + case "rete-due": + mediaBu = "rsi"; + mediaId = "livestream_ReteDue"; + break; + case "rete-tre": + mediaBu = "rsi"; + mediaId = "livestream_ReteTre"; + break; + case "rtr": + mediaBu = "rtr"; + mediaId = "a029e818-77a5-4c2e-ad70-d573bb865e31"; + break; + } + + if (mediaBu && mediaId) { + return openMedia(server, mediaBu, "audio", mediaId, null); + } + else { + // Returns default radio homepage + return openPage(server, bu, "radio:home", null, null); + } + } + + /** + * Catch live tv popup urls + * + * Ex: https://www.srf.ch/play/tv/popupvideoplayer?id=b833a5af-63c6-4310-bb80-05341310a4f5 + */ + if (pathname.includes("/tv/popupvideoplayer")) { + var mediaId = queryParams["id"]; + if (mediaId) { + return openMedia(server, bu, "video", mediaId, null); + } + else { + // Returns default TV homepage + return openPage(server, bu, "tv:home", null, null); + } + } + + /** + * Catch classic show urls + * + * Ex: https://www.rts.ch/play/tv/emission/faut-pas-croire?id=6176 + */ + var showTransmission = null; + switch (true) { + case pathname.includes("/tv/sendung") || pathname.includes("/tv/emission") || pathname.includes("/tv/programma") || pathname.includes("/tv/emissiuns"): + showTransmission = "tv"; + break; + case pathname.includes("/radio/sendung") || pathname.includes("/radio/emission") || pathname.includes("/radio/programma") || pathname.includes("/radio/emissiuns"): + showTransmission = "radio"; + break; + } + + if (showTransmission) { + var showId = queryParams["id"]; + if (showId) { + return openShow(server, bu, showTransmission, showId); + } + else { + showTransmission = null; + } + } + + /** + * Catch redirect show urls + * + * Ex: https://www.rts.ch/play/tv/quicklink/6176 + */ + switch (true) { + case pathname.includes("/tv/quicklink/"): + showTransmission = "tv"; + break; + case pathname.includes("/radio/quicklink/"): + showTransmission = "radio"; + break; + } + + if (showTransmission) { + var showId = pathname.substr(pathname.lastIndexOf('/') + 1); + if (showId) { + return openShow(server, bu, showTransmission, showId); + } + else { + showTransmission = null; + } + } + + /** + * Catch home TV urls + * + * Ex: https://www.srf.ch/play/tv + */ + if (pathname.endsWith("/tv")) { + return openPage(server, bu, "tv:home", null, null); + } + + /** + * Catch home radio urls + * + * Ex: https://www.srf.ch/play/radio?station=ee1fb348-2b6a-4958-9aac-ec6c87e190da + */ + if (pathname.endsWith("/radio")) { + var channelId = queryParams["station"]; + return openPage(server, bu, "radio:home", channelId, null); + } + + /** + * Catch AZ TV urls + * + * Ex: https://www.rts.ch/play/tv/emissions?index=G + */ + if (pathname.endsWith("/tv/sendungen") || pathname.endsWith("/tv/emissions") || pathname.endsWith("/tv/programmi") || pathname.endsWith("/tv/emissiuns")) { + var index = queryParams["index"]; + if (index) { + index = index.toLowerCase(); + index = (index.length > 1) ? null : index; + } + var options = new Array( { key: "index", value: index } ); + return openPage(server, bu, "tv:az", null, options); + } + + /** + * Catch AZ radio urls + * + * Ex: https://www.rts.ch/play/radio/emissions?index=S&station=8ceb28d9b3f1dd876d1df1780f908578cbefc3d7 + */ + if (pathname.endsWith("/radio/sendungen") || pathname.endsWith("/radio/emissions") || pathname.endsWith("/radio/programmi") || pathname.endsWith("/radio/emissiuns")) { + var channelId = queryParams["station"]; + var index = queryParams["index"]; + if (index) { + index = index.toLowerCase(); + index = (index.length > 1) ? null : index; + } + var options = new Array( { key: "index", value: index } ); + return openPage(server, bu, "radio:az", channelId, options); + } + + /** + * Catch by date TV urls + * + * Ex: https://www.rtr.ch/play/tv/emissiuns-tenor-data?date=07-03-2019 + */ + if (pathname.endsWith("/tv/sendungen-nach-datum") || pathname.endsWith("/tv/emissions-par-dates") || pathname.endsWith("/tv/programmi-per-data") || pathname.endsWith("/tv/emissiuns-tenor-data")) { + var date = queryParams["date"]; + if (date) { + // Returns an ISO format + var dateArray = date.split("-"); + if (dateArray.length == 3 && dateArray[2].length == 4 && dateArray[1].length == 2 && dateArray[0].length == 2) { + date = dateArray[2] + "-" + dateArray[1] + "-" + dateArray[0]; + } + else { + date = null; + } + } + var options = new Array( { key: "date", value: date } ); + return openPage(server, bu, "tv:bydate", null, options); + } + + /** + * Catch by date radio urls + * + * Ex: https://www.rts.ch/play/radio/emissions-par-dates?date=07-03-2019&station=8ceb28d9b3f1dd876d1df1780f908578cbefc3d7 + */ + if (pathname.endsWith("/radio/sendungen-nach-datum") || pathname.endsWith("/radio/emissions-par-dates") || pathname.endsWith("/radio/programmi-per-data") || pathname.endsWith("/radio/emissiuns-tenor-data")) { + var channelId = queryParams["station"]; + var date = queryParams["date"]; + if (date) { + // Returns an ISO format + var dateArray = date.split("-"); + if (dateArray.length == 3 && dateArray[2].length == 4 && dateArray[1].length == 2 && dateArray[0].length == 2) { + date = dateArray[2] + "-" + dateArray[1] + "-" + dateArray[0]; + } + else { + date = null; + } + } + var options = new Array( { key: "date", value: date } ); + return openPage(server, bu, "radio:bydate", channelId, options); + } + + /** + * Catch search urls + * + * Ex: https://www.rsi.ch/play/ricerca?query=federer%20finale + * + * Ex: https://www.rtr.ch/play/tv/retschertga?query=federer%2520tennis + * Ex: https://www.rtr.ch/play/tv/retschertga?query=federer%2520tennis#radio + * Ex: https://www.rtr.ch/play/radio/retschertga?query=federer%2520tennis + * Ex: https://www.rtr.ch/play/radio/retschertga?query=federer%2520tennis#tv + */ + if (pathname.endsWith("/suche") || pathname.endsWith("/recherche") || pathname.endsWith("/ricerca") || pathname.endsWith("/retschertga") || pathname.endsWith("/search")) { + var query = queryParams["query"]; + var mediaType = queryParams["mediaType"]; + var transmission = null; + if (pathname.includes("/tv/")) { + transmission = "tv"; + query = decodeURIComponent(query); + if (anchor == "#radio") { + transmission = "radio"; + } + } + else if (pathname.includes("/radio/")) { + transmission = "radio"; + query = decodeURIComponent(query); + if (anchor == "#tv") { + transmission = "tv"; + } + } + if (mediaType) { + mediaType = mediaType.toLowerCase(); + if (mediaType != "video" && mediaType != "audio") { + mediaType = null; + } + } + var transmissionComponent = (transmission != null) ? transmission + ":" : ""; + var options = new Array( { key: "query", value: query }, { key: "mediaType", value: mediaType } ); + return openPage(server, bu, transmissionComponent + "search", null, options); + } + + /** + * Catch TV topics urls + * + * Ex: https://www.rts.ch/play/tv/categories/info + */ + if (pathname.endsWith("/tv/themen") || pathname.endsWith("/tv/categories") || pathname.endsWith("/tv/categorie") || pathname.endsWith("/tv/tematicas") || pathname.endsWith("/tv/topics")) { + return openPage(server, bu, "tv:home", null, null); + } + if (pathname.includes("/tv/themen") || pathname.includes("/tv/categories") || pathname.includes("/tv/categorie") || pathname.includes("/tv/tematicas") || pathname.includes("/tv/topics")) { + var lastPathComponent = pathname.split("/").slice(-1)[0]; + + var topicId = null; + + /* INJECT TVTOPICS OBJECT */ + + if (typeof tvTopics !== 'undefined' && lastPathComponent.length > 0) { + topicId = tvTopics[bu][lastPathComponent]; + } + + if (topicId) { + return openTopic(server, bu, "tv", topicId); + } + else { + return openPage(server, bu, "tv:home", null, null); + } + } + + /** + * Catch TV event urls + * + * Ex: https://www.srf.ch/play/tv/event/10-jahre-auf-und-davon + *. Ex: https://www.rsi.ch/play/tv/event/event-playrsi-8858482 + */ + if (pathname.endsWith("/tv/event")) { + return openPage(server, bu, "tv:home", null, null); + } + if (pathname.includes("/tv/event")) { + var lastPathComponent = pathname.split("/").slice(-1)[0]; + + var eventId = null; + + /* INJECT TVEVENTS OBJECT */ + + if (typeof tvEvents !== 'undefined' && lastPathComponent.length > 0) { + eventId = tvEvents[bu][lastPathComponent]; + } + + if (eventId) { + return openModule(server, bu, "event", eventId); + } + else { + return openPage(server, bu, "tv:home", null, null); + } + } + + /** + * Catch base play urls + * + * Ex: https://www.srf.ch/play/ + *. Ex: https://www.rsi.ch/play + */ + if (pathname.endsWith("/play/") || pathname.endsWith("/play")) { + return openPage(server, bu, "tv:home", null, null); + } + + // Redirect fallback. + console.log("Can't parse Play URL. Redirect."); + return schemeForBu(bu) + "://redirect"; +}; + +var openMedia = function(server, bu, mediaType, mediaId, startTime) { + var redirect = schemeForBu(bu) + "://open?media=urn:" + bu + ":" + mediaType + ":" + mediaId; + if (startTime) { + redirect = redirect + "&start-time=" + startTime; + } + if (server) { + redirect = redirect + "&server=" + encodeURIComponent(server); + } + return redirect; +}; + +var openMediaURN = function(server, bu, mediaURN, startTime) { + var redirect = schemeForBu(bu) + "://open?media=" + mediaURN; + if (startTime) { + redirect = redirect + "&start-time=" + startTime; + } + if (server) { + redirect = redirect + "&server=" + encodeURIComponent(server); + } + return redirect; +}; + +var openShow = function(server, bu, showTransmission, showId) { + var redirect = schemeForBu(bu) + "://open?show=urn:" + bu + ":show:" + showTransmission + ":" + showId; + if (server) { + redirect = redirect + "&server=" + encodeURIComponent(server); + } + return redirect; +}; + +var openTopic = function(server, bu, topicTransmission, topicId) { + var redirect = schemeForBu(bu) + "://open?topic=urn:" + bu + ":topic:" + topicTransmission + ":" + topicId; + if (server) { + redirect = redirect + "&server=" + encodeURIComponent(server); + } + return redirect; +}; + +var openModule = function(server, bu, moduleType, moduleId) { + var redirect = schemeForBu(bu) + "://open?module=urn:" + bu + ":module:" + moduleType + ":" + moduleId; + if (server) { + redirect = redirect + "&server=" + encodeURIComponent(server); + } + return redirect; +}; + +var openPage = function(server, bu, page, channelId, options) { + if (! page) { + page = "tv:home"; + } + + var redirect = schemeForBu(bu) + "://open?page=urn:" + bu + ":page:" + page; + if (channelId) { + redirect = redirect + "&channel-id=" + channelId; + } + if (options && Array.isArray(options)) { + options.forEach(function(option) { + if (option.key && option.value) { + redirect = redirect + "&" + option.key + "=" + encodeURIComponent(option.value); + } + }); + } + if (server) { + redirect = redirect + "&server=" + encodeURIComponent(server); + } + return redirect; +}; + +var schemeForBu = function(bu) { + switch (bu) { + case "srf": + return "playsrf"; + break; + case "rts": + return "playrts"; + break; + case "rsi": + return "playrsi"; + break; + case "rtr": + return "playrtr"; + break; + case "swi": + return "playswi"; + break; + case "mmf": + case "tp": + return "letterbox"; + break; + default: + return null; + } +}; + +var serverForUrl = function(hostname, pathname, queryParams) { + var server = "production"; + if (hostname.includes("stage")) { + server = "stage"; + } + else if (hostname.includes("test")) { + server = "test"; + } + else if (hostname.includes("play-mmf")) { + if (pathname.startsWith("/mmf/")) { + server = "play mmf"; + } + else { + var server = queryParams["server"]; + switch (server) { + case "production": + server = "production"; + break; + case "stage": + server = "stage"; + break; + case "test": + server = "test"; + break; + default: + server = null; + } + } + } + return server; +}; From 6fab1cacf3da8cbb9a79c061e26f063a1c5f0218 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 12:17:46 +0200 Subject: [PATCH 17/45] Resolve test execution --- ...RecommendationServiceAudioResultTests.java | 7 ++-- ...rviceRSIOneFullLengthVideoResultTests.java | 7 ++-- ...ommendationServiceRSIVideoResultTests.java | 7 ++-- ...RecommendationServiceVideoResultTests.java | 7 ++-- .../playfff/helper/BaseResourceString.java | 34 ------------------- 5 files changed, 12 insertions(+), 50 deletions(-) delete mode 100644 src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java diff --git a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceAudioResultTests.java b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceAudioResultTests.java index 6f59c00..c9ec18c 100644 --- a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceAudioResultTests.java +++ b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceAudioResultTests.java @@ -23,7 +23,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; @@ -206,7 +205,7 @@ public void audioClipforNotFoundEpisodeTest() throws URISyntaxException { private void testAudioRecommendation(String urn, boolean isFullLength, boolean episodeCompositionHasNextUrl, List expectedUrns) throws URISyntaxException { String mediaFileName = urn.replace(":", "-") + ".json"; - String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName, new HashMap<>()); + String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/media/byUrn/"+ urn + ".json"))) .andExpect(method(HttpMethod.GET)) @@ -216,7 +215,7 @@ private void testAudioRecommendation(String urn, boolean isFullLength, boolean e ); String episodeCompositionFileName = episodeCompositionHasNextUrl ? "episode-composition-rts-radio-next-url.json" : "episode-composition-rts-radio.json"; - String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName, new HashMap<>()); + String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/episodeComposition/latestByShow/byUrn/urn:rts:show:radio:1234.json?pageSize=100"))) .andExpect(method(HttpMethod.GET)) @@ -227,7 +226,7 @@ private void testAudioRecommendation(String urn, boolean isFullLength, boolean e try { String mediaCompositionFileName = "media-composition-" + urn.replace(":", "-") + ".json"; - String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName, new HashMap<>()); + String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName); mockServer.expect(ExpectedCount.between(0, 2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/mediaComposition/byUrn/" + urn + ".json"))) .andExpect(method(HttpMethod.GET)) diff --git a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIOneFullLengthVideoResultTests.java b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIOneFullLengthVideoResultTests.java index dfc69d5..fa12725 100644 --- a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIOneFullLengthVideoResultTests.java +++ b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIOneFullLengthVideoResultTests.java @@ -23,7 +23,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; @@ -228,7 +227,7 @@ public void videoClipforNotFoundEpisodeTest() throws URISyntaxException { private void testVideoRecommendation(String urn, boolean isFullLength, boolean episodeCompositionHasNextUrl, List expectedUrns, List expectedStandaloneUrns) throws URISyntaxException { String mediaFileName = urn.replace(":", "-") + ".json"; - String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName, new HashMap<>()); + String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/media/byUrn/"+ urn + ".json"))) .andExpect(method(HttpMethod.GET)) @@ -238,7 +237,7 @@ private void testVideoRecommendation(String urn, boolean isFullLength, boolean e ); String episodeCompositionFileName = episodeCompositionHasNextUrl ? "episode-composition-rsi-tv-one-full-length-next-url.json" : "episode-composition-rsi-tv-one-full-length.json"; - String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName, new HashMap<>()); + String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/episodeComposition/latestByShow/byUrn/urn:rsi:show:tv:1234.json?pageSize=100"))) .andExpect(method(HttpMethod.GET)) @@ -249,7 +248,7 @@ private void testVideoRecommendation(String urn, boolean isFullLength, boolean e try { String mediaCompositionFileName = "media-composition-" + urn.replace(":", "-") + ".json"; - String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName, new HashMap<>()); + String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName); mockServer.expect(ExpectedCount.between(0, 2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/mediaComposition/byUrn/" + urn + ".json"))) .andExpect(method(HttpMethod.GET)) diff --git a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIVideoResultTests.java b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIVideoResultTests.java index 95f72a2..e6742c0 100644 --- a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIVideoResultTests.java +++ b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceRSIVideoResultTests.java @@ -23,7 +23,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; @@ -228,7 +227,7 @@ public void videoClipforNotFoundEpisodeTest() throws URISyntaxException { private void testVideoRecommendation(String urn, boolean isFullLength, boolean episodeCompositionHasNextUrl, List expectedUrns, List expectedStandaloneUrns) throws URISyntaxException { String mediaFileName = urn.replace(":", "-") + ".json"; - String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName, new HashMap<>()); + String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/media/byUrn/"+ urn + ".json"))) .andExpect(method(HttpMethod.GET)) @@ -238,7 +237,7 @@ private void testVideoRecommendation(String urn, boolean isFullLength, boolean e ); String episodeCompositionFileName = episodeCompositionHasNextUrl ? "episode-composition-rsi-tv-next-url.json" : "episode-composition-rsi-tv.json"; - String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName, new HashMap<>()); + String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/episodeComposition/latestByShow/byUrn/urn:rsi:show:tv:1234.json?pageSize=100"))) .andExpect(method(HttpMethod.GET)) @@ -249,7 +248,7 @@ private void testVideoRecommendation(String urn, boolean isFullLength, boolean e try { String mediaCompositionFileName = "media-composition-" + urn.replace(":", "-") + ".json"; - String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName, new HashMap<>()); + String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName); mockServer.expect(ExpectedCount.between(0, 2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/mediaComposition/byUrn/" + urn + ".json"))) .andExpect(method(HttpMethod.GET)) diff --git a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceVideoResultTests.java b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceVideoResultTests.java index 156c60f..f5f975a 100644 --- a/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceVideoResultTests.java +++ b/src/test/java/ch/srgssr/playfff/controller/RecommendationServiceVideoResultTests.java @@ -23,7 +23,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; @@ -225,7 +224,7 @@ public void videoClipforNotFoundEpisodeTest() throws URISyntaxException { private void testVideoRecommendation(String urn, boolean isFullLength, boolean episodeCompositionHasNextUrl, List expectedUrns, List expectedStandaloneUrns) throws URISyntaxException { String mediaFileName = urn.replace(":", "-") + ".json"; - String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName, new HashMap<>()); + String mediaJson = BaseResourceString.getString(applicationContext, mediaFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/media/byUrn/"+ urn + ".json"))) .andExpect(method(HttpMethod.GET)) @@ -235,7 +234,7 @@ private void testVideoRecommendation(String urn, boolean isFullLength, boolean e ); String episodeCompositionFileName = episodeCompositionHasNextUrl ? "episode-composition-rtr-tv-next-url.json" : "episode-composition-rtr-tv.json"; - String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName, new HashMap<>()); + String episodeCompositionJson = BaseResourceString.getString(applicationContext, episodeCompositionFileName); mockServer.expect(ExpectedCount.times(2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/episodeComposition/latestByShow/byUrn/urn:rtr:show:tv:1234.json?pageSize=100"))) .andExpect(method(HttpMethod.GET)) @@ -246,7 +245,7 @@ private void testVideoRecommendation(String urn, boolean isFullLength, boolean e try { String mediaCompositionFileName = "media-composition-" + urn.replace(":", "-") + ".json"; - String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName, new HashMap<>()); + String mediaCompositionJson = BaseResourceString.getString(applicationContext, mediaCompositionFileName); mockServer.expect(ExpectedCount.between(0, 2), requestTo(new URI("http://il.srgssr.ch:80/integrationlayer/2.0/mediaComposition/byUrn/" + urn + ".json"))) .andExpect(method(HttpMethod.GET)) diff --git a/src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java b/src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java deleted file mode 100644 index 8c66e34..0000000 --- a/src/test/java/ch/srgssr/playfff/helper/BaseResourceString.java +++ /dev/null @@ -1,34 +0,0 @@ -package ch.srgssr.playfff.helper; - -import org.springframework.context.ApplicationContext; -import org.springframework.core.io.Resource; -import org.springframework.util.StreamUtils; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -/** - * Created by seb on 10/08/16. - */ -public class BaseResourceString { - public static String getString(ApplicationContext applicationContext, String name, Map variables) { - try { - Resource resource = applicationContext.getResource("classpath:" + name); - if (resource == null) { - throw new RuntimeException("No resource: " + name); - } - String s = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8); - for (String k : variables.keySet()) { - String value = variables.get(k); - if (value == null) { - throw new IllegalArgumentException(k + " has null value (" + name + ")"); - } - s = s.replaceAll(k, value); - } - return s; - } catch (IOException e) { - throw new RuntimeException("IO Exception for " + name, e); - } - } -} From d33b5cd1a14fbdee93cd1d9824937417cdb1ced8 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 12:46:24 +0200 Subject: [PATCH 18/45] Rename controller --- .../playfff/controller/UpdateController.java | 14 +++++++------- .../{WhatIsNew.java => WhatIsNewController.java} | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) rename src/main/java/ch/srgssr/playfff/controller/{WhatIsNew.java => WhatIsNewController.java} (96%) diff --git a/src/main/java/ch/srgssr/playfff/controller/UpdateController.java b/src/main/java/ch/srgssr/playfff/controller/UpdateController.java index d153922..733f2ae 100644 --- a/src/main/java/ch/srgssr/playfff/controller/UpdateController.java +++ b/src/main/java/ch/srgssr/playfff/controller/UpdateController.java @@ -45,13 +45,6 @@ public String updateRemove(@RequestParam(value = "package") String packageName, return "removed"; } - @RequestMapping("/api/v1/update/check") - public ResponseEntity updateText(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) { - Update update = updateService.getUpdate(packageName, version); - return new ResponseEntity<>(new UpdateResult(update), HttpStatus.OK); - } - - @PostMapping("/api/v1/update") public ResponseEntity create(@RequestBody Update update) { return new ResponseEntity<>(updateService.create(update), HttpStatus.OK); @@ -76,4 +69,11 @@ public ResponseEntity delete(@PathVariable("id") int id) { public ResponseEntity> findAllDesc() { return new ResponseEntity<>(updateService.findAllDesc(), HttpStatus.OK); } + + // Public API + @RequestMapping("/api/v1/update/check") + public ResponseEntity updateText(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) { + Update update = updateService.getUpdate(packageName, version); + return new ResponseEntity<>(new UpdateResult(update), HttpStatus.OK); + } } diff --git a/src/main/java/ch/srgssr/playfff/controller/WhatIsNew.java b/src/main/java/ch/srgssr/playfff/controller/WhatIsNewController.java similarity index 96% rename from src/main/java/ch/srgssr/playfff/controller/WhatIsNew.java rename to src/main/java/ch/srgssr/playfff/controller/WhatIsNewController.java index 46bebc9..64d14f2 100644 --- a/src/main/java/ch/srgssr/playfff/controller/WhatIsNew.java +++ b/src/main/java/ch/srgssr/playfff/controller/WhatIsNewController.java @@ -17,7 +17,7 @@ * License information is available from the LICENSE file. */ @Controller -public class WhatIsNew { +public class WhatIsNewController { @Autowired ReleaseNoteService releaseNoteService; @@ -38,12 +38,14 @@ public String whatisnewSave(@RequestParam(value = "package") String packageName, return "pushed"; } + // Public API @RequestMapping("/api/v1/whatisnew/text") @ResponseBody public WhatIsNewResult whatisnewText(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) { return new WhatIsNewResult(releaseNoteService.getDisplayableText(packageName, version)); } + // Public API @RequestMapping("/api/v1/whatisnew/html") @ResponseBody public String whatisnewHtml(@RequestParam(value = "package") String packageName, @RequestParam(value = "version") String version) { From a67cb91369f234f92ecfb09e50a59cc44adcf802 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 13:20:22 +0200 Subject: [PATCH 19/45] Redirect to root page after login --- .../java/ch/srgssr/playfff/config/AuthenticationConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index 8650d31..9c38e79 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -45,9 +45,11 @@ protected void configure(HttpSecurity http) throws Exception { .and() .formLogin() .loginPage("/login") + .defaultSuccessUrl("/", true) .permitAll() .and() .logout() + .deleteCookies("JSESSIONID") .permitAll(); http .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); From 546c840f5139483b7c46e64b536191d6bc89c4e5 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 16:47:15 +0200 Subject: [PATCH 20/45] Add unit test for deeplink report post --- .../controller/DeeplinkIntegrationTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java index b5bd420..9205c66 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java @@ -6,6 +6,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -14,6 +15,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringRunner.class) @@ -44,4 +46,53 @@ public void getParsePlayUrl() throws Exception { .andExpect(status().isNotModified()) .andExpect(content().string("")); } + + @Test + public void reportSave() throws Exception { + mvc.perform(post("/api/v1/deeplink/report")).andExpect(status().isBadRequest()); + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content("{}")).andExpect(status().isNotAcceptable()); + + String nonAcceptableJson1 = "{\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + + "}"; + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson1)).andExpect(status().isNotAcceptable()); + + String nonAcceptableJson2 = "{\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + + "}"; + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson2)).andExpect(status().isNotAcceptable()); + + String nonAcceptableJson3 = "{\n" + + "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + + "}"; + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson3)).andExpect(status().isNotAcceptable()); + + String acceptableJson1 = "{\n" + + "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + + "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + + "}"; + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").value(1)).andExpect(jsonPath("$.count").value(1)); + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").value(1)).andExpect(jsonPath("$.count").value(2)); + + String acceptableJson2 = "{\n" + + "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + + "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown2\"\n" + + "}"; + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").value(2)).andExpect(jsonPath("$.count").value(1)); + + } } From 34bbeb0c911adc2ead2a146c380b32a0c451afa4 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 18:09:20 +0200 Subject: [PATCH 21/45] Add deeplink reports APIs and tests --- pom.xml | 7 ++ .../controller/DeeplinkController.java | 41 +++++++---- .../repository/ParsingReportRepository.java | 2 + .../playfff/service/ParsingReportService.java | 18 +++++ .../srgssr/playfff/service/UpdateService.java | 4 ++ .../controller/DeeplinkIntegrationTest.java | 71 ++++++++++++++++++- .../srgssr/playfff/controller/JsonUtil.java | 26 +++++++ 7 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 src/test/java/ch/srgssr/playfff/controller/JsonUtil.java diff --git a/pom.xml b/pom.xml index c7fa1a7..c1b80c3 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,13 @@ integrationlayer-domain-objects 1.20.272 + + + org.hamcrest + hamcrest-library + 1.3 + test + diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java index 19442b4..35dfad8 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java @@ -29,22 +29,22 @@ public class DeeplinkController { @Autowired private ParsingReportService parsingReportService; - @RequestMapping(value="/api/v1/deeplink/parse_play_url.js") - @ResponseBody - public ResponseEntity parsePlayUrlJavascript() { + @GetMapping(path = {"/api/v1/deeplink/report/{id}"}) + public ResponseEntity findOne(@PathVariable("id") int id) { + return new ResponseEntity<>(parsingReportService.findById(id), HttpStatus.OK); + } - DeeplinkContent deeplinkContent = service.getParsePlayUrlContent(); + @DeleteMapping(path = {"/api/v1/deeplink/report/{id}"}) + public ResponseEntity delete(@PathVariable("id") int id) { + return new ResponseEntity<>(parsingReportService.delete(id), HttpStatus.OK); + } - if (deeplinkContent != null) { - return ResponseEntity.ok() - .cacheControl(CacheControl.empty().cachePublic()) - .eTag(deeplinkContent.getHash()) - .body(deeplinkContent.getContent()); - } else { - return ResponseEntity.notFound().build(); - } + @GetMapping("/api/v1/deeplink/report") + public ResponseEntity> findAllByOrderByCountDesc() { + return new ResponseEntity<>(parsingReportService.findAllByOrderByCountDesc(), HttpStatus.OK); } + // Public API @PostMapping("/api/v1/deeplink/report") public ResponseEntity create(@RequestBody ParsingReport parsingReport, WebRequest webRequest) { @@ -56,4 +56,21 @@ public ResponseEntity create(@RequestBody ParsingReport parsingRe return new ResponseEntity<>(parsingReportService.save(parsingReport), HttpStatus.CREATED); } } + + // Public API + @RequestMapping(value="/api/v1/deeplink/parse_play_url.js") + @ResponseBody + public ResponseEntity parsePlayUrlJavascript() { + + DeeplinkContent deeplinkContent = service.getParsePlayUrlContent(); + + if (deeplinkContent != null) { + return ResponseEntity.ok() + .cacheControl(CacheControl.empty().cachePublic()) + .eTag(deeplinkContent.getHash()) + .body(deeplinkContent.getContent()); + } else { + return ResponseEntity.notFound().build(); + } + } } diff --git a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java index 31a0a12..24a2af3 100644 --- a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java +++ b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java @@ -17,4 +17,6 @@ public interface ParsingReportRepository extends CrudRepository findAllByOrderByClientTimeDesc(); List findAllByClientTimeLessThan(Date date); + + List findAllByOrderByCountDesc(); } diff --git a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java index b495c82..08c0f8a 100644 --- a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java +++ b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java @@ -56,4 +56,22 @@ public synchronized void purgeOlderReports() { private ParsingReport getParsingReport(String clientId, String jsVersion, String url) { return repository.findFirstByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); } + + public ParsingReport findById(long id) { + return repository.findOne(id); + } + + @Transactional + public ParsingReport delete(long id) { + ParsingReport parsingReport = findById(id); + if (parsingReport != null) { + repository.delete(parsingReport); + return parsingReport; + } + return null; + } + + public Iterable findAllByOrderByCountDesc() { + return repository.findAllByOrderByCountDesc(); + } } diff --git a/src/main/java/ch/srgssr/playfff/service/UpdateService.java b/src/main/java/ch/srgssr/playfff/service/UpdateService.java index b4f562c..637a7fa 100644 --- a/src/main/java/ch/srgssr/playfff/service/UpdateService.java +++ b/src/main/java/ch/srgssr/playfff/service/UpdateService.java @@ -29,6 +29,7 @@ public void remove(String packageName, String version) { repository.removeByPackageNameAndVersion(packageName, version); } + @Transactional public Update getUpdate(String packageName, String version) { List updates = repository.findByPackageNameAndVersion(packageName, version); @@ -39,6 +40,7 @@ public Update getUpdate(String packageName, String version) { } } + @Transactional public Update create(Update update) { return repository.save(update); } @@ -47,10 +49,12 @@ public Update findById(long id) { return repository.findOne(id); } + @Transactional public Update update(Update update) { return repository.save(update); } + @Transactional public Update delete(long id) { Update update = findById(id); if (update != null) { diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java index 9205c66..77f5ff5 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java @@ -1,22 +1,31 @@ package ch.srgssr.playfff.controller; +import ch.srgssr.playfff.model.ParsingReport; +import ch.srgssr.playfff.repository.ParsingReportRepository; import org.hamcrest.core.IsNull; +import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @@ -26,6 +35,9 @@ public class DeeplinkIntegrationTest { @Autowired private WebApplicationContext context; + @Autowired + private ParsingReportRepository repository; + @Before public void setup() { mvc = MockMvcBuilders @@ -34,6 +46,11 @@ public void setup() { .build(); } + @After + public void tearDown() { + repository.deleteAll(); + } + @Test public void getParsePlayUrl() throws Exception { MvcResult mvcResult = mvc.perform(get("/api/v1/deeplink/parse_play_url.js")) @@ -81,9 +98,20 @@ public void reportSave() throws Exception { "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + "}"; - mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").value(1)).andExpect(jsonPath("$.count").value(1)); + MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(1)).andReturn(); + ParsingReport parsingReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), ParsingReport.class); + + String acceptableJson1bis = "{\n" + + "\t\"clientTime\": \"2019-08-20T16:15:53+02:00\",\n" + + "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + + "}"; + + MvcResult mvcResult1bis = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1bis)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(2)).andReturn(); + ParsingReport parsingReport1bis = JsonUtil.getMapper().readValue(mvcResult1bis.getResponse().getContentAsString(), ParsingReport.class); - mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").value(1)).andExpect(jsonPath("$.count").value(2)); + Assert.assertEquals(parsingReport1.id, parsingReport1bis.id); String acceptableJson2 = "{\n" + "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + @@ -92,7 +120,44 @@ public void reportSave() throws Exception { "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown2\"\n" + "}"; - mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").value(2)).andExpect(jsonPath("$.count").value(1)); + MvcResult mvcResult2 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(1)).andReturn(); + ParsingReport parsingReport2 = JsonUtil.getMapper().readValue(mvcResult2.getResponse().getContentAsString(), ParsingReport.class); + + Assert.assertNotEquals(parsingReport1.id, parsingReport2.id); + } + + @Test + @WithMockUser(username = "deeplink", password = "password", roles = "USER") + public void reportChange() throws Exception { + String json1 = "{\n" + + "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + + "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + + "}"; + + MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(json1)).andExpect(status().isCreated()).andReturn(); + ParsingReport parsingReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), ParsingReport.class); + + mvc.perform(get("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(json1)); + + mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(1))); + + String json2 = "{\n" + + "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + + "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + + "\t\"jsVersion\": \"v1.0\",\n" + + "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown2\"\n" + + "}"; + + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(json2)).andExpect(status().isCreated()); + + mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(2))); + + mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isForbidden()); + + mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id).with(csrf())).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(json1)); + mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(1))); } } diff --git a/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java b/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java new file mode 100644 index 0000000..db539d0 --- /dev/null +++ b/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java @@ -0,0 +1,26 @@ +package ch.srgssr.playfff.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public final class JsonUtil { + // Lay Initialization + private static volatile ObjectMapper mapper = null; + + // Prevent multiple instances + private JsonUtil() {} + + /** + * Double-checked locking + * @return + */ + public static synchronized ObjectMapper getMapper() { + if (mapper == null) { + synchronized (JsonUtil.class) { + if (mapper == null) { + mapper = new ObjectMapper(); + } + } + } + return mapper; + } +} From 897cb5cca6c89f7362f4dbacb9b4ef7b0fc588a2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 18:42:00 +0200 Subject: [PATCH 22/45] Use objects in test instead of strings --- .../controller/DeeplinkIntegrationTest.java | 97 ++++++++++--------- .../srgssr/playfff/controller/JsonUtil.java | 2 + 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java index 77f5ff5..1d6c803 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java @@ -2,6 +2,7 @@ import ch.srgssr.playfff.model.ParsingReport; import ch.srgssr.playfff.repository.ParsingReportRepository; +import java.text.SimpleDateFormat; import org.hamcrest.core.IsNull; import org.junit.After; import org.junit.Assert; @@ -30,6 +31,8 @@ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class DeeplinkIntegrationTest { + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + private MockMvc mvc; @Autowired @@ -70,55 +73,55 @@ public void reportSave() throws Exception { mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content("{}")).andExpect(status().isNotAcceptable()); - String nonAcceptableJson1 = "{\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + - "}"; + ParsingReport nonAcceptableParsingReport1 = new ParsingReport(); + nonAcceptableParsingReport1.url = "https://www.rts.ch/rts/play/unknown1"; + String nonAcceptableJson1 = JsonUtil.getMapper().writeValueAsString(nonAcceptableParsingReport1); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson1)).andExpect(status().isNotAcceptable()); - String nonAcceptableJson2 = "{\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + - "}"; + ParsingReport nonAcceptableParsingReport2 = new ParsingReport(); + nonAcceptableParsingReport2.jsVersion = "v1.0"; + nonAcceptableParsingReport2.url = "https://www.rts.ch/rts/play/unknown1"; + String nonAcceptableJson2 = JsonUtil.getMapper().writeValueAsString(nonAcceptableParsingReport2); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson2)).andExpect(status().isNotAcceptable()); - String nonAcceptableJson3 = "{\n" + - "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + - "}"; + ParsingReport nonAcceptableParsingReport3 = new ParsingReport(); + nonAcceptableParsingReport3.clientId = "ch.rts.rtsplayer"; + nonAcceptableParsingReport3.jsVersion = "v1.0"; + nonAcceptableParsingReport3.url = "https://www.rts.ch/rts/play/unknown1"; + String nonAcceptableJson3 = JsonUtil.getMapper().writeValueAsString(nonAcceptableParsingReport3); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson3)).andExpect(status().isNotAcceptable()); - String acceptableJson1 = "{\n" + - "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + - "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + - "}"; + ParsingReport acceptableParsingReport1 = new ParsingReport(); + acceptableParsingReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableParsingReport1.clientId = "ch.rts.rtsplayer"; + acceptableParsingReport1.jsVersion = "v1.0"; + acceptableParsingReport1.url = "https://www.rts.ch/rts/play/unknown1"; + String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport1); MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(1)).andReturn(); ParsingReport parsingReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), ParsingReport.class); - String acceptableJson1bis = "{\n" + - "\t\"clientTime\": \"2019-08-20T16:15:53+02:00\",\n" + - "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + - "}"; + ParsingReport acceptableParsingReport1bis = new ParsingReport(); + acceptableParsingReport1bis.clientTime = dateFormat.parse("2019-08-20T16:15:53+02:00"); + acceptableParsingReport1bis.clientId = "ch.rts.rtsplayer"; + acceptableParsingReport1bis.jsVersion = "v1.0"; + acceptableParsingReport1bis.url = "https://www.rts.ch/rts/play/unknown1"; + String acceptableJson1bis = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport1bis); MvcResult mvcResult1bis = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1bis)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(2)).andReturn(); ParsingReport parsingReport1bis = JsonUtil.getMapper().readValue(mvcResult1bis.getResponse().getContentAsString(), ParsingReport.class); Assert.assertEquals(parsingReport1.id, parsingReport1bis.id); - String acceptableJson2 = "{\n" + - "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + - "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown2\"\n" + - "}"; + ParsingReport acceptableParsingReport2 = new ParsingReport(); + acceptableParsingReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableParsingReport2.clientId = "ch.rts.rtsplayer"; + acceptableParsingReport2.jsVersion = "v1.0"; + acceptableParsingReport2.url = "https://www.rts.ch/rts/play/unknown2"; + String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport2); MvcResult mvcResult2 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(1)).andReturn(); ParsingReport parsingReport2 = JsonUtil.getMapper().readValue(mvcResult2.getResponse().getContentAsString(), ParsingReport.class); @@ -129,34 +132,34 @@ public void reportSave() throws Exception { @Test @WithMockUser(username = "deeplink", password = "password", roles = "USER") public void reportChange() throws Exception { - String json1 = "{\n" + - "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + - "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown\"\n" + - "}"; - - MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(json1)).andExpect(status().isCreated()).andReturn(); + ParsingReport acceptableParsingReport1 = new ParsingReport(); + acceptableParsingReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableParsingReport1.clientId = "ch.rts.rtsplayer"; + acceptableParsingReport1.jsVersion = "v1.0"; + acceptableParsingReport1.url = "https://www.rts.ch/rts/play/unknown1"; + String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport1); + + MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andReturn(); ParsingReport parsingReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), ParsingReport.class); - mvc.perform(get("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(json1)); + mvc.perform(get("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(acceptableJson1)); mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(1))); - String json2 = "{\n" + - "\t\"clientTime\": \"2019-07-20T16:15:53+02:00\",\n" + - "\t\"clientId\": \"ch.rts.rtsplayer.debug\",\n" + - "\t\"jsVersion\": \"v1.0\",\n" + - "\t\"url\": \"https:\\/\\/www.rts.ch\\/rts\\/play\\/unknown2\"\n" + - "}"; + ParsingReport acceptableParsingReport2 = new ParsingReport(); + acceptableParsingReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableParsingReport2.clientId = "ch.rts.rtsplayer"; + acceptableParsingReport2.jsVersion = "v1.0"; + acceptableParsingReport2.url = "https://www.rts.ch/rts/play/unknown2"; + String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport2); - mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(json2)).andExpect(status().isCreated()); + mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()); mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(2))); mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isForbidden()); - mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id).with(csrf())).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(json1)); + mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id).with(csrf())).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(acceptableJson1)); mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(1))); } diff --git a/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java b/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java index db539d0..e929627 100644 --- a/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java +++ b/src/test/java/ch/srgssr/playfff/controller/JsonUtil.java @@ -1,5 +1,6 @@ package ch.srgssr.playfff.controller; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; public final class JsonUtil { @@ -18,6 +19,7 @@ public static synchronized ObjectMapper getMapper() { synchronized (JsonUtil.class) { if (mapper == null) { mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); } } } From fe9951bac12a977d6c9ffccb34e44fd83f2040a2 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 19:30:19 +0200 Subject: [PATCH 23/45] Add web admin interface to list deeplink failed reports --- portal-app/src/app/app.component.html | 4 +- portal-app/src/app/app.module.ts | 11 ++++-- portal-app/src/app/app.routing.module.ts | 6 ++- .../src/app/deeplink/deeplink.component.html | 26 +++++++++++++ .../app/deeplink/deeplink.component.spec.ts | 25 +++++++++++++ .../src/app/deeplink/deeplink.component.ts | 37 +++++++++++++++++++ .../src/app/deeplink/deeplink.service.ts | 37 +++++++++++++++++++ .../src/app/models/deeplink-report.model.ts | 8 ++++ .../src/app/update/add-update.component.html | 2 +- .../src/app/update/update.component.css | 0 .../src/app/update/update.component.html | 8 +++- 11 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 portal-app/src/app/deeplink/deeplink.component.html create mode 100644 portal-app/src/app/deeplink/deeplink.component.spec.ts create mode 100644 portal-app/src/app/deeplink/deeplink.component.ts create mode 100644 portal-app/src/app/deeplink/deeplink.service.ts create mode 100644 portal-app/src/app/models/deeplink-report.model.ts delete mode 100644 portal-app/src/app/update/update.component.css diff --git a/portal-app/src/app/app.component.html b/portal-app/src/app/app.component.html index 0f12d6a..3d7867b 100644 --- a/portal-app/src/app/app.component.html +++ b/portal-app/src/app/app.component.html @@ -7,8 +7,8 @@

Pfff - Admin


diff --git a/portal-app/src/app/app.module.ts b/portal-app/src/app/app.module.ts index 339c3c4..0693e6b 100644 --- a/portal-app/src/app/app.module.ts +++ b/portal-app/src/app/app.module.ts @@ -4,9 +4,11 @@ import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { UpdateComponent } from './update/update.component'; +import { DeeplinkComponent } from './deeplink/deeplink.component'; import { AppRoutingModule } from './app.routing.module'; -import {UpdateService} from './update/update.service'; -import {HttpClientModule} from "@angular/common/http"; +import { UpdateService } from './update/update.service'; +import { DeeplinkService } from './deeplink/deeplink.service'; +import { HttpClientModule } from "@angular/common/http"; import {AddUpdateComponent} from './update/add-update.component'; @@ -14,7 +16,8 @@ import {AddUpdateComponent} from './update/add-update.component'; declarations: [ AppComponent, UpdateComponent, - AddUpdateComponent + AddUpdateComponent, + DeeplinkComponent ], imports: [ BrowserModule, @@ -22,7 +25,7 @@ import {AddUpdateComponent} from './update/add-update.component'; HttpClientModule, FormsModule ], - providers: [UpdateService], + providers: [UpdateService, DeeplinkService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/portal-app/src/app/app.routing.module.ts b/portal-app/src/app/app.routing.module.ts index c5b9358..894dec8 100644 --- a/portal-app/src/app/app.routing.module.ts +++ b/portal-app/src/app/app.routing.module.ts @@ -2,11 +2,13 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { UpdateComponent } from './update/update.component'; -import {AddUpdateComponent} from './update/add-update.component'; +import { AddUpdateComponent } from './update/add-update.component'; +import { DeeplinkComponent } from './deeplink/deeplink.component'; const routes: Routes = [ { path: 'updates', component: UpdateComponent }, - { path: 'add_update', component: AddUpdateComponent } + { path: 'add_update', component: AddUpdateComponent }, + { path: 'deeplink', component: DeeplinkComponent } ]; @NgModule({ diff --git a/portal-app/src/app/deeplink/deeplink.component.html b/portal-app/src/app/deeplink/deeplink.component.html new file mode 100644 index 0000000..ec6f6cb --- /dev/null +++ b/portal-app/src/app/deeplink/deeplink.component.html @@ -0,0 +1,26 @@ +
+

+ Deeplink reports +

+

Only failed url parsings are reported and ordered by count.

+ + + + + + + + + + + + + +
+ {{col}} +
+ {{deeplinkReport[col]}} + + +
+
diff --git a/portal-app/src/app/deeplink/deeplink.component.spec.ts b/portal-app/src/app/deeplink/deeplink.component.spec.ts new file mode 100644 index 0000000..da7ff72 --- /dev/null +++ b/portal-app/src/app/deeplink/deeplink.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeeplinkComponent } from './deeplink.component'; + +describe('DeeplinkComponent', () => { + let component: DeeplinkComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DeeplinkComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeeplinkComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/portal-app/src/app/deeplink/deeplink.component.ts b/portal-app/src/app/deeplink/deeplink.component.ts new file mode 100644 index 0000000..827faac --- /dev/null +++ b/portal-app/src/app/deeplink/deeplink.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import {DeeplinkReport} from '../models/deeplink-report.model'; +import { DeeplinkService } from './deeplink.service'; + +@Component({ + selector: 'app-user', + templateUrl: './deeplink.component.html', + styles: [] +}) +export class DeeplinkComponent implements OnInit { + deeplinkReports: DeeplinkReport[]; + columns: string[]; + + constructor(private router: Router, private deeplinkService: DeeplinkService) { + + } + + ngOnInit() { + this.columns = this.deeplinkService.getColumns(); + + this.deeplinkService.getDeeplinkReports() + .subscribe( data => { + console.log(data); + this.deeplinkReports = data; + }); + }; + + deleteDeeplinkReport(deeplinkReport: DeeplinkReport): void { + this.deeplinkService.deleteDeeplinkReport(deeplinkReport) + .subscribe( data => { + this.deeplinkReports = this.deeplinkReports.filter(dr => dr !== deeplinkReport); + }) + }; + +} diff --git a/portal-app/src/app/deeplink/deeplink.service.ts b/portal-app/src/app/deeplink/deeplink.service.ts new file mode 100644 index 0000000..1eedb57 --- /dev/null +++ b/portal-app/src/app/deeplink/deeplink.service.ts @@ -0,0 +1,37 @@ +import {Injectable} from '@angular/core'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; + +import {DeeplinkReport} from '../models/deeplink-report.model'; + + +const httpOptions = { + headers: new HttpHeaders({'Content-Type': 'application/json'}) +}; + +@Injectable() +export class DeeplinkService { + + constructor(private http: HttpClient) { + } + + private deeplinkReportUrl = '/api/v1/deeplink/report'; + + public getDeeplinkReports() { + return this.http.get(this.deeplinkReportUrl); + } + + public deleteDeeplinkReport(deeplinkReport) { + return this.http.delete(this.deeplinkReportUrl + "/" + deeplinkReport.id); + } + + public getColumns() { + return [ + "id", + "clientId", + "clientTime", + "count", + "jsVersion", + "url", + ] + } +} \ No newline at end of file diff --git a/portal-app/src/app/models/deeplink-report.model.ts b/portal-app/src/app/models/deeplink-report.model.ts new file mode 100644 index 0000000..436160f --- /dev/null +++ b/portal-app/src/app/models/deeplink-report.model.ts @@ -0,0 +1,8 @@ +export class DeeplinkReport { + id: string; + clientId: string; + clientTime: string; + count: number; + jsVersion: string; + url: string; +} diff --git a/portal-app/src/app/update/add-update.component.html b/portal-app/src/app/update/add-update.component.html index 514ac47..5f76e48 100644 --- a/portal-app/src/app/update/add-update.component.html +++ b/portal-app/src/app/update/add-update.component.html @@ -1,5 +1,5 @@
-

Add Update

+

Add an update

diff --git a/portal-app/src/app/update/update.component.css b/portal-app/src/app/update/update.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/portal-app/src/app/update/update.component.html b/portal-app/src/app/update/update.component.html index 367bfea..c086a3f 100644 --- a/portal-app/src/app/update/update.component.html +++ b/portal-app/src/app/update/update.component.html @@ -1,5 +1,9 @@
-

Update Details

+

+ Updates +

+

Recommended update or mandatory update messages displayed at application launched.

+

Add an update

@@ -15,7 +19,7 @@

Update Details

{{update[col]}} From 057b3d480e145ce3415b0fa916c7cfecb56b93cd Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 19:53:26 +0200 Subject: [PATCH 24/45] Renaming to deep link --- docs/README.md | 3 +- portal-app/src/app/app.component.html | 2 +- .../src/app/deeplink/deeplink.component.html | 2 +- .../srgssr/playfff/config/DeeplinkConfig.java | 12 +- .../controller/DeepLinkController.java | 74 ++++++++++++ .../controller/DeeplinkController.java | 76 ------------ ...inkContent.java => DeepLinkJSContent.java} | 4 +- ...ParsingReport.java => DeepLinkReport.java} | 4 +- .../repository/DeepLinkReportRepository.java | 22 ++++ .../repository/ParsingReportRepository.java | 22 ---- .../service/DeepLinkReportService.java | 75 ++++++++++++ ...plinkService.java => DeepLinkService.java} | 23 ++-- .../playfff/service/ParsingReportService.java | 77 ------------ ...Test.java => DeepLinkIntegrationTest.java} | 110 +++++++++--------- .../controller/DeepLinkServiceTests.java | 38 ++++++ .../controller/DeeplinkServiceTests.java | 38 ------ 16 files changed, 289 insertions(+), 293 deletions(-) create mode 100644 src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java delete mode 100644 src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java rename src/main/java/ch/srgssr/playfff/model/{DeeplinkContent.java => DeepLinkJSContent.java} (81%) rename src/main/java/ch/srgssr/playfff/model/{ParsingReport.java => DeepLinkReport.java} (92%) create mode 100644 src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java delete mode 100644 src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java create mode 100644 src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java rename src/main/java/ch/srgssr/playfff/service/{DeeplinkService.java => DeepLinkService.java} (90%) delete mode 100644 src/main/java/ch/srgssr/playfff/service/ParsingReportService.java rename src/test/java/ch/srgssr/playfff/controller/{DeeplinkIntegrationTest.java => DeepLinkIntegrationTest.java} (59%) create mode 100644 src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java delete mode 100644 src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java diff --git a/docs/README.md b/docs/README.md index df3de76..e470828 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,7 +18,8 @@ A Java development environment with Maven is needed. A wide list of parameters are available. * `PFFF_USER` (optional, string): A user login to admin service. -* `PFFF_PASSWORD` (optional, string): A user password to admin service. +* `PFFF_PASSWORD` (optional, string): A user password to admin service. +* `MAX_DEEP_LINK_REPORTS` (optional, integer): Maximum number of deep link reports in the database, default is `2500`. ## API * `urn` (string): an unique identifier. diff --git a/portal-app/src/app/app.component.html b/portal-app/src/app/app.component.html index 3d7867b..2dd87e0 100644 --- a/portal-app/src/app/app.component.html +++ b/portal-app/src/app/app.component.html @@ -8,7 +8,7 @@

Pfff - Admin


diff --git a/portal-app/src/app/deeplink/deeplink.component.html b/portal-app/src/app/deeplink/deeplink.component.html index ec6f6cb..991001d 100644 --- a/portal-app/src/app/deeplink/deeplink.component.html +++ b/portal-app/src/app/deeplink/deeplink.component.html @@ -1,6 +1,6 @@

- Deeplink reports + Deep link reports

Only failed url parsings are reported and ordered by count.

diff --git a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java index 65beb8e..b4379e9 100644 --- a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java @@ -1,7 +1,7 @@ package ch.srgssr.playfff.config; -import ch.srgssr.playfff.service.DeeplinkService; -import ch.srgssr.playfff.service.ParsingReportService; +import ch.srgssr.playfff.service.DeepLinkService; +import ch.srgssr.playfff.service.DeepLinkReportService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @@ -17,14 +17,14 @@ public class DeeplinkConfig { @Autowired - private DeeplinkService deeplinkService; + private DeepLinkService deepLinkService; @Autowired - private ParsingReportService parsingReportService; + private DeepLinkReportService deepLinkReportService; @Scheduled(fixedDelayString = "${DEEPLINK_REFRESH_DELAY_MS:300000}") public void DeeplinkRefresh() { - deeplinkService.refreshParsePlayUrlContent(); - parsingReportService.purgeOlderReports(); + deepLinkService.refreshParsePlayUrlJSContent(); + deepLinkReportService.purgeOlderReports(); } } diff --git a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java new file mode 100644 index 0000000..74b18dd --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java @@ -0,0 +1,74 @@ +package ch.srgssr.playfff.controller; + +import ch.srgssr.playfff.model.DeepLinkJSContent; +import ch.srgssr.playfff.model.DeepLinkReport; +import ch.srgssr.playfff.service.DeepLinkService; +import ch.srgssr.playfff.service.DeepLinkReportService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.WebRequest; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +@Controller +@CrossOrigin(origins = "*") +public class DeepLinkController { + + @Autowired + private DeepLinkService service; + + @Autowired + private DeepLinkReportService deepLinkReportService; + + @GetMapping(path = {"/api/v1/deeplink/report/{id}"}) + public ResponseEntity findOne(@PathVariable("id") int id) { + return new ResponseEntity<>(deepLinkReportService.findById(id), HttpStatus.OK); + } + + @DeleteMapping(path = {"/api/v1/deeplink/report/{id}"}) + public ResponseEntity delete(@PathVariable("id") int id) { + return new ResponseEntity<>(deepLinkReportService.delete(id), HttpStatus.OK); + } + + @GetMapping("/api/v1/deeplink/report") + public ResponseEntity> findAllByOrderByCountDesc() { + return new ResponseEntity<>(deepLinkReportService.findAllByOrderByCountDesc(), HttpStatus.OK); + } + + // Public API + @PostMapping("/api/v1/deeplink/report") + public ResponseEntity create(@RequestBody DeepLinkReport deepLinkReport, WebRequest webRequest) { + + if (deepLinkReport == null + || deepLinkReport.clientTime == null || deepLinkReport.clientId == null + || deepLinkReport.jsVersion == null || deepLinkReport.url == null) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } else { + return new ResponseEntity<>(deepLinkReportService.save(deepLinkReport), HttpStatus.CREATED); + } + } + + // Public API + @RequestMapping(value="/api/v1/deeplink/parse_play_url.js") + @ResponseBody + public ResponseEntity parsePlayUrlJavascript() { + + DeepLinkJSContent deepLinkJSContent = service.getParsePlayUrlJSContent(); + + if (deepLinkJSContent != null) { + return ResponseEntity.ok() + .cacheControl(CacheControl.empty().cachePublic()) + .eTag(deepLinkJSContent.getHash()) + .body(deepLinkJSContent.getContent()); + } else { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java deleted file mode 100644 index 35dfad8..0000000 --- a/src/main/java/ch/srgssr/playfff/controller/DeeplinkController.java +++ /dev/null @@ -1,76 +0,0 @@ -package ch.srgssr.playfff.controller; - -import ch.srgssr.playfff.model.DeeplinkContent; -import ch.srgssr.playfff.model.ParsingReport; -import ch.srgssr.playfff.model.Update; -import ch.srgssr.playfff.service.DeeplinkService; -import ch.srgssr.playfff.service.ParsingReportService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.CacheControl; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.WebRequest; - -/** - * Copyright (c) SRG SSR. All rights reserved. - *

- * License information is available from the LICENSE file. - */ -@Controller -@CrossOrigin(origins = "*") -public class DeeplinkController { - - @Autowired - private DeeplinkService service; - - @Autowired - private ParsingReportService parsingReportService; - - @GetMapping(path = {"/api/v1/deeplink/report/{id}"}) - public ResponseEntity findOne(@PathVariable("id") int id) { - return new ResponseEntity<>(parsingReportService.findById(id), HttpStatus.OK); - } - - @DeleteMapping(path = {"/api/v1/deeplink/report/{id}"}) - public ResponseEntity delete(@PathVariable("id") int id) { - return new ResponseEntity<>(parsingReportService.delete(id), HttpStatus.OK); - } - - @GetMapping("/api/v1/deeplink/report") - public ResponseEntity> findAllByOrderByCountDesc() { - return new ResponseEntity<>(parsingReportService.findAllByOrderByCountDesc(), HttpStatus.OK); - } - - // Public API - @PostMapping("/api/v1/deeplink/report") - public ResponseEntity create(@RequestBody ParsingReport parsingReport, WebRequest webRequest) { - - if (parsingReport == null - || parsingReport.clientTime == null || parsingReport.clientId == null - || parsingReport.jsVersion == null || parsingReport.url == null) { - return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); - } else { - return new ResponseEntity<>(parsingReportService.save(parsingReport), HttpStatus.CREATED); - } - } - - // Public API - @RequestMapping(value="/api/v1/deeplink/parse_play_url.js") - @ResponseBody - public ResponseEntity parsePlayUrlJavascript() { - - DeeplinkContent deeplinkContent = service.getParsePlayUrlContent(); - - if (deeplinkContent != null) { - return ResponseEntity.ok() - .cacheControl(CacheControl.empty().cachePublic()) - .eTag(deeplinkContent.getHash()) - .body(deeplinkContent.getContent()); - } else { - return ResponseEntity.notFound().build(); - } - } -} diff --git a/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java b/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java similarity index 81% rename from src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java rename to src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java index 04e35a6..c49c9df 100644 --- a/src/main/java/ch/srgssr/playfff/model/DeeplinkContent.java +++ b/src/main/java/ch/srgssr/playfff/model/DeepLinkJSContent.java @@ -5,12 +5,12 @@ *

* License information is available from the LICENSE file. */ -public class DeeplinkContent { +public class DeepLinkJSContent { private String content; private String hash; - public DeeplinkContent(String content, String hash) { + public DeepLinkJSContent(String content, String hash) { this.content = content; this.hash = hash; } diff --git a/src/main/java/ch/srgssr/playfff/model/ParsingReport.java b/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java similarity index 92% rename from src/main/java/ch/srgssr/playfff/model/ParsingReport.java rename to src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java index 65b5cc2..a657e8e 100644 --- a/src/main/java/ch/srgssr/playfff/model/ParsingReport.java +++ b/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java @@ -11,8 +11,8 @@ * License information is available from the LICENSE file. */ @Entity -@Table(name = "parsing_reports") -public class ParsingReport { +@Table(name = "deeplink_reports") +public class DeepLinkReport { private static final long serialVersionUID = -3009157732242241606L; @Id diff --git a/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java new file mode 100644 index 0000000..b087615 --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java @@ -0,0 +1,22 @@ +package ch.srgssr.playfff.repository; + +import ch.srgssr.playfff.model.DeepLinkReport; +import org.springframework.data.repository.CrudRepository; + +import java.util.Date; +import java.util.List; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +public interface DeepLinkReportRepository extends CrudRepository { + DeepLinkReport findFirstByClientIdAndJsVersionAndUrl(String clientId, String jsVersion, String url); + + List findAllByOrderByClientTimeDesc(); + + List findAllByClientTimeLessThan(Date date); + + List findAllByOrderByCountDesc(); +} diff --git a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java deleted file mode 100644 index 24a2af3..0000000 --- a/src/main/java/ch/srgssr/playfff/repository/ParsingReportRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package ch.srgssr.playfff.repository; - -import ch.srgssr.playfff.model.ParsingReport; -import org.springframework.data.repository.CrudRepository; - -import java.util.Date; -import java.util.List; - -/** - * Copyright (c) SRG SSR. All rights reserved. - *

- * License information is available from the LICENSE file. - */ -public interface ParsingReportRepository extends CrudRepository { - ParsingReport findFirstByClientIdAndJsVersionAndUrl(String clientId, String jsVersion, String url); - - List findAllByOrderByClientTimeDesc(); - - List findAllByClientTimeLessThan(Date date); - - List findAllByOrderByCountDesc(); -} diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java new file mode 100644 index 0000000..e6504e4 --- /dev/null +++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java @@ -0,0 +1,75 @@ +package ch.srgssr.playfff.service; + +import ch.srgssr.playfff.model.DeepLinkReport; +import ch.srgssr.playfff.repository.DeepLinkReportRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Repository; + +import javax.transaction.Transactional; +import java.util.List; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +@Repository +public class DeepLinkReportService { + @Autowired + private DeepLinkReportRepository repository; + + private int maxDeepLinkReports; + + public DeepLinkReportService( + @Value("${MAX_DEEP_LINK_REPORTS:2500}") String maxDeepLinkReportsString) { + + this.maxDeepLinkReports = Integer.valueOf(maxDeepLinkReportsString); + } + + @Transactional + public DeepLinkReport save(DeepLinkReport deepLinkReport) { + DeepLinkReport currentDeepLinkReport = getParsingReport(deepLinkReport.clientId, deepLinkReport.jsVersion, deepLinkReport.url); + if (currentDeepLinkReport != null) { + currentDeepLinkReport.count += 1; + currentDeepLinkReport.clientTime = deepLinkReport.clientTime; + return repository.save(currentDeepLinkReport); + } else { + deepLinkReport.count = 1; + return repository.save(deepLinkReport); + } + } + + @Transactional + public synchronized void purgeOlderReports() { + // Keep only latest reports + if (repository.count() > maxDeepLinkReports) { + List allReports = repository.findAllByOrderByClientTimeDesc(); + DeepLinkReport report = allReports.get(maxDeepLinkReports - 1); + List olderReports = repository.findAllByClientTimeLessThan(report.clientTime); + repository.delete(olderReports); + } + } + + private DeepLinkReport getParsingReport(String clientId, String jsVersion, String url) { + return repository.findFirstByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); + } + + public DeepLinkReport findById(long id) { + return repository.findOne(id); + } + + @Transactional + public DeepLinkReport delete(long id) { + DeepLinkReport deepLinkReport = findById(id); + if (deepLinkReport != null) { + repository.delete(deepLinkReport); + return deepLinkReport; + } + return null; + } + + public Iterable findAllByOrderByCountDesc() { + return repository.findAllByOrderByCountDesc(); + } +} diff --git a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java similarity index 90% rename from src/main/java/ch/srgssr/playfff/service/DeeplinkService.java rename to src/main/java/ch/srgssr/playfff/service/DeepLinkService.java index 324cec2..0523025 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeeplinkService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkService.java @@ -3,7 +3,7 @@ import ch.srg.il.domain.v2_0.ModuleConfig; import ch.srg.il.domain.v2_0.ModuleConfigList; import ch.srgssr.playfff.helper.BaseResourceString; -import ch.srgssr.playfff.model.DeeplinkContent; +import ch.srgssr.playfff.model.DeepLinkJSContent; import ch.srgssr.playfff.model.Environment; import ch.srgssr.playfff.model.playportal.PlayTopic; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,7 +15,6 @@ import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @@ -40,10 +39,10 @@ * License information is available from the LICENSE file. */ @Service -public class DeeplinkService { - private static final Logger logger = LoggerFactory.getLogger(DeeplinkService.class); +public class DeepLinkService { + private static final Logger logger = LoggerFactory.getLogger(DeepLinkService.class); - private DeeplinkContent parsePlayUrlContent; + private DeepLinkJSContent parsePlayUrlContent; private RestTemplate restTemplate; @@ -53,21 +52,21 @@ public class DeeplinkService { @Autowired private IntegrationLayerRequest integrationLayerRequest; - public DeeplinkService(RestTemplateBuilder restTemplateBuilder) { + public DeepLinkService(RestTemplateBuilder restTemplateBuilder) { restTemplate = restTemplateBuilder.build(); } - @Cacheable("DeeplinkParsePlayUrl") - public DeeplinkContent getParsePlayUrlContent() { + @Cacheable("DeeplinkParsePlayUrlJSContent") + public DeepLinkJSContent getParsePlayUrlJSContent() { if (parsePlayUrlContent == null) { - refreshParsePlayUrlContent(); + refreshParsePlayUrlJSContent(); } return parsePlayUrlContent; } - @CachePut("DeeplinkParsePlayUrl") - public synchronized DeeplinkContent refreshParsePlayUrlContent() { + @CachePut("DeeplinkParsePlayUrlJSContent") + public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() { String javascript = BaseResourceString.getString(applicationContext, "parse_play_url.js"); Map buMap = new HashMap(); @@ -150,7 +149,7 @@ public synchronized DeeplinkContent refreshParsePlayUrlContent() { String strDate = dateFormat.format(buildDate); javascript = javascript.replaceAll("var parsePlayUrlBuild = \"mmf\";", "var parsePlayUrlBuild = \"" + buildHash + "\";\nvar parsePlayUrlBuildDate = \"" + strDate + "\";"); - parsePlayUrlContent = new DeeplinkContent(javascript, buildHash); + parsePlayUrlContent = new DeepLinkJSContent(javascript, buildHash); return parsePlayUrlContent; } diff --git a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java b/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java deleted file mode 100644 index 08c0f8a..0000000 --- a/src/main/java/ch/srgssr/playfff/service/ParsingReportService.java +++ /dev/null @@ -1,77 +0,0 @@ -package ch.srgssr.playfff.service; - -import ch.srgssr.playfff.model.ParsingReport; -import ch.srgssr.playfff.model.Update; -import ch.srgssr.playfff.repository.ParsingReportRepository; -import ch.srgssr.playfff.repository.UpdateRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Repository; - -import javax.transaction.Transactional; -import java.util.List; - -/** - * Copyright (c) SRG SSR. All rights reserved. - *

- * License information is available from the LICENSE file. - */ -@Repository -public class ParsingReportService { - @Autowired - private ParsingReportRepository repository; - - private int maxParsingReports; - - public ParsingReportService( - @Value("${MAX_PARSING_REPORTS:2500}") String maxParsingReportsString) { - - this.maxParsingReports = Integer.valueOf(maxParsingReportsString); - } - - @Transactional - public ParsingReport save(ParsingReport parsingReport) { - ParsingReport currentParsingReport = getParsingReport(parsingReport.clientId, parsingReport.jsVersion, parsingReport.url); - if (currentParsingReport != null) { - currentParsingReport.count += 1; - currentParsingReport.clientTime = parsingReport.clientTime; - return repository.save(currentParsingReport); - } else { - parsingReport.count = 1; - return repository.save(parsingReport); - } - } - - @Transactional - public synchronized void purgeOlderReports() { - // Keep only latest reports - if (repository.count() > maxParsingReports) { - List allReports = repository.findAllByOrderByClientTimeDesc(); - ParsingReport report = allReports.get(maxParsingReports - 1); - List olderReports = repository.findAllByClientTimeLessThan(report.clientTime); - repository.delete(olderReports); - } - } - - private ParsingReport getParsingReport(String clientId, String jsVersion, String url) { - return repository.findFirstByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); - } - - public ParsingReport findById(long id) { - return repository.findOne(id); - } - - @Transactional - public ParsingReport delete(long id) { - ParsingReport parsingReport = findById(id); - if (parsingReport != null) { - repository.delete(parsingReport); - return parsingReport; - } - return null; - } - - public Iterable findAllByOrderByCountDesc() { - return repository.findAllByOrderByCountDesc(); - } -} diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java similarity index 59% rename from src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java rename to src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java index 1d6c803..433b209 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java @@ -1,7 +1,7 @@ package ch.srgssr.playfff.controller; -import ch.srgssr.playfff.model.ParsingReport; -import ch.srgssr.playfff.repository.ParsingReportRepository; +import ch.srgssr.playfff.model.DeepLinkReport; +import ch.srgssr.playfff.repository.DeepLinkReportRepository; import java.text.SimpleDateFormat; import org.hamcrest.core.IsNull; import org.junit.After; @@ -30,7 +30,7 @@ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class DeeplinkIntegrationTest { +public class DeepLinkIntegrationTest { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); private MockMvc mvc; @@ -39,7 +39,7 @@ public class DeeplinkIntegrationTest { private WebApplicationContext context; @Autowired - private ParsingReportRepository repository; + private DeepLinkReportRepository repository; @Before public void setup() { @@ -73,93 +73,93 @@ public void reportSave() throws Exception { mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content("{}")).andExpect(status().isNotAcceptable()); - ParsingReport nonAcceptableParsingReport1 = new ParsingReport(); - nonAcceptableParsingReport1.url = "https://www.rts.ch/rts/play/unknown1"; - String nonAcceptableJson1 = JsonUtil.getMapper().writeValueAsString(nonAcceptableParsingReport1); + DeepLinkReport nonAcceptableDeepLinkReport1 = new DeepLinkReport(); + nonAcceptableDeepLinkReport1.url = "https://www.rts.ch/rts/play/unknown1"; + String nonAcceptableJson1 = JsonUtil.getMapper().writeValueAsString(nonAcceptableDeepLinkReport1); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson1)).andExpect(status().isNotAcceptable()); - ParsingReport nonAcceptableParsingReport2 = new ParsingReport(); - nonAcceptableParsingReport2.jsVersion = "v1.0"; - nonAcceptableParsingReport2.url = "https://www.rts.ch/rts/play/unknown1"; - String nonAcceptableJson2 = JsonUtil.getMapper().writeValueAsString(nonAcceptableParsingReport2); + DeepLinkReport nonAcceptableDeepLinkReport2 = new DeepLinkReport(); + nonAcceptableDeepLinkReport2.jsVersion = "v1.0"; + nonAcceptableDeepLinkReport2.url = "https://www.rts.ch/rts/play/unknown1"; + String nonAcceptableJson2 = JsonUtil.getMapper().writeValueAsString(nonAcceptableDeepLinkReport2); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson2)).andExpect(status().isNotAcceptable()); - ParsingReport nonAcceptableParsingReport3 = new ParsingReport(); - nonAcceptableParsingReport3.clientId = "ch.rts.rtsplayer"; - nonAcceptableParsingReport3.jsVersion = "v1.0"; - nonAcceptableParsingReport3.url = "https://www.rts.ch/rts/play/unknown1"; - String nonAcceptableJson3 = JsonUtil.getMapper().writeValueAsString(nonAcceptableParsingReport3); + DeepLinkReport nonAcceptableDeepLinkReport3 = new DeepLinkReport(); + nonAcceptableDeepLinkReport3.clientId = "ch.rts.rtsplayer"; + nonAcceptableDeepLinkReport3.jsVersion = "v1.0"; + nonAcceptableDeepLinkReport3.url = "https://www.rts.ch/rts/play/unknown1"; + String nonAcceptableJson3 = JsonUtil.getMapper().writeValueAsString(nonAcceptableDeepLinkReport3); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson3)).andExpect(status().isNotAcceptable()); - ParsingReport acceptableParsingReport1 = new ParsingReport(); - acceptableParsingReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); - acceptableParsingReport1.clientId = "ch.rts.rtsplayer"; - acceptableParsingReport1.jsVersion = "v1.0"; - acceptableParsingReport1.url = "https://www.rts.ch/rts/play/unknown1"; - String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport1); + DeepLinkReport acceptableDeepLinkReport1 = new DeepLinkReport(); + acceptableDeepLinkReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableDeepLinkReport1.clientId = "ch.rts.rtsplayer"; + acceptableDeepLinkReport1.jsVersion = "v1.0"; + acceptableDeepLinkReport1.url = "https://www.rts.ch/rts/play/unknown1"; + String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport1); MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(1)).andReturn(); - ParsingReport parsingReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), ParsingReport.class); + DeepLinkReport deepLinkReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), DeepLinkReport.class); - ParsingReport acceptableParsingReport1bis = new ParsingReport(); - acceptableParsingReport1bis.clientTime = dateFormat.parse("2019-08-20T16:15:53+02:00"); - acceptableParsingReport1bis.clientId = "ch.rts.rtsplayer"; - acceptableParsingReport1bis.jsVersion = "v1.0"; - acceptableParsingReport1bis.url = "https://www.rts.ch/rts/play/unknown1"; - String acceptableJson1bis = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport1bis); + DeepLinkReport acceptableDeepLinkReport1Bis = new DeepLinkReport(); + acceptableDeepLinkReport1Bis.clientTime = dateFormat.parse("2019-08-20T16:15:53+02:00"); + acceptableDeepLinkReport1Bis.clientId = "ch.rts.rtsplayer"; + acceptableDeepLinkReport1Bis.jsVersion = "v1.0"; + acceptableDeepLinkReport1Bis.url = "https://www.rts.ch/rts/play/unknown1"; + String acceptableJson1bis = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport1Bis); MvcResult mvcResult1bis = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1bis)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(2)).andReturn(); - ParsingReport parsingReport1bis = JsonUtil.getMapper().readValue(mvcResult1bis.getResponse().getContentAsString(), ParsingReport.class); + DeepLinkReport deepLinkReport1Bis = JsonUtil.getMapper().readValue(mvcResult1bis.getResponse().getContentAsString(), DeepLinkReport.class); - Assert.assertEquals(parsingReport1.id, parsingReport1bis.id); + Assert.assertEquals(deepLinkReport1.id, deepLinkReport1Bis.id); - ParsingReport acceptableParsingReport2 = new ParsingReport(); - acceptableParsingReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); - acceptableParsingReport2.clientId = "ch.rts.rtsplayer"; - acceptableParsingReport2.jsVersion = "v1.0"; - acceptableParsingReport2.url = "https://www.rts.ch/rts/play/unknown2"; - String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport2); + DeepLinkReport acceptableDeepLinkReport2 = new DeepLinkReport(); + acceptableDeepLinkReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableDeepLinkReport2.clientId = "ch.rts.rtsplayer"; + acceptableDeepLinkReport2.jsVersion = "v1.0"; + acceptableDeepLinkReport2.url = "https://www.rts.ch/rts/play/unknown2"; + String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport2); MvcResult mvcResult2 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$.id").isNotEmpty()).andExpect(jsonPath("$.count").value(1)).andReturn(); - ParsingReport parsingReport2 = JsonUtil.getMapper().readValue(mvcResult2.getResponse().getContentAsString(), ParsingReport.class); + DeepLinkReport deepLinkReport2 = JsonUtil.getMapper().readValue(mvcResult2.getResponse().getContentAsString(), DeepLinkReport.class); - Assert.assertNotEquals(parsingReport1.id, parsingReport2.id); + Assert.assertNotEquals(deepLinkReport1.id, deepLinkReport2.id); } @Test @WithMockUser(username = "deeplink", password = "password", roles = "USER") public void reportChange() throws Exception { - ParsingReport acceptableParsingReport1 = new ParsingReport(); - acceptableParsingReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); - acceptableParsingReport1.clientId = "ch.rts.rtsplayer"; - acceptableParsingReport1.jsVersion = "v1.0"; - acceptableParsingReport1.url = "https://www.rts.ch/rts/play/unknown1"; - String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport1); + DeepLinkReport acceptableDeepLinkReport1 = new DeepLinkReport(); + acceptableDeepLinkReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableDeepLinkReport1.clientId = "ch.rts.rtsplayer"; + acceptableDeepLinkReport1.jsVersion = "v1.0"; + acceptableDeepLinkReport1.url = "https://www.rts.ch/rts/play/unknown1"; + String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport1); MvcResult mvcResult1 = mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson1)).andExpect(status().isCreated()).andReturn(); - ParsingReport parsingReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), ParsingReport.class); + DeepLinkReport deepLinkReport1 = JsonUtil.getMapper().readValue(mvcResult1.getResponse().getContentAsString(), DeepLinkReport.class); - mvc.perform(get("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(acceptableJson1)); + mvc.perform(get("/api/v1/deeplink/report/" + deepLinkReport1.id)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(acceptableJson1)); mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(1))); - ParsingReport acceptableParsingReport2 = new ParsingReport(); - acceptableParsingReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); - acceptableParsingReport2.clientId = "ch.rts.rtsplayer"; - acceptableParsingReport2.jsVersion = "v1.0"; - acceptableParsingReport2.url = "https://www.rts.ch/rts/play/unknown2"; - String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableParsingReport2); + DeepLinkReport acceptableDeepLinkReport2 = new DeepLinkReport(); + acceptableDeepLinkReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); + acceptableDeepLinkReport2.clientId = "ch.rts.rtsplayer"; + acceptableDeepLinkReport2.jsVersion = "v1.0"; + acceptableDeepLinkReport2.url = "https://www.rts.ch/rts/play/unknown2"; + String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport2); mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(acceptableJson2)).andExpect(status().isCreated()); mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(2))); - mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id)).andExpect(status().isForbidden()); + mvc.perform(delete("/api/v1/deeplink/report/" + deepLinkReport1.id)).andExpect(status().isForbidden()); - mvc.perform(delete("/api/v1/deeplink/report/" + parsingReport1.id).with(csrf())).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(acceptableJson1)); + mvc.perform(delete("/api/v1/deeplink/report/" + deepLinkReport1.id).with(csrf())).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(content().json(acceptableJson1)); mvc.perform(get("/api/v1/deeplink/report")).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)).andExpect(jsonPath("$").isArray()).andExpect(jsonPath("$", hasSize(1))); } diff --git a/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java b/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java new file mode 100644 index 0000000..c105cf9 --- /dev/null +++ b/src/test/java/ch/srgssr/playfff/controller/DeepLinkServiceTests.java @@ -0,0 +1,38 @@ +package ch.srgssr.playfff.controller; + +import ch.srgssr.playfff.model.DeepLinkJSContent; +import ch.srgssr.playfff.service.DeepLinkService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class DeepLinkServiceTests { + + @Autowired + private DeepLinkService deepLinkService; + + @Test + public void getParsePlayUrlContentTest() { + DeepLinkJSContent deepLinkJSContent = deepLinkService.getParsePlayUrlJSContent(); + + Assert.assertNotNull(deepLinkJSContent.getContent()); + Assert.assertNotNull(deepLinkJSContent.getHash()); + Assert.assertTrue(deepLinkJSContent.getContent().contains(deepLinkJSContent.getHash())); + } + + @Test + public void refreshParsePlayUrlContentTest() { + DeepLinkJSContent deepLinkJSContent = deepLinkService.refreshParsePlayUrlJSContent(); + + Assert.assertNotNull(deepLinkJSContent.getContent()); + Assert.assertNotNull(deepLinkJSContent.getHash()); + Assert.assertTrue(deepLinkJSContent.getContent().contains(deepLinkJSContent.getHash())); + Assert.assertFalse(deepLinkJSContent.getContent().contains("INJECT TVTOPICS OBJECT")); + Assert.assertFalse(deepLinkJSContent.getContent().contains("INJECT TVEVENTS OBJECT")); + } +} diff --git a/src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java b/src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java deleted file mode 100644 index 85dc1ec..0000000 --- a/src/test/java/ch/srgssr/playfff/controller/DeeplinkServiceTests.java +++ /dev/null @@ -1,38 +0,0 @@ -package ch.srgssr.playfff.controller; - -import ch.srgssr.playfff.model.DeeplinkContent; -import ch.srgssr.playfff.service.DeeplinkService; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class DeeplinkServiceTests { - - @Autowired - private DeeplinkService deeplinkService; - - @Test - public void getParsePlayUrlContentTest() { - DeeplinkContent deeplinkContent = deeplinkService.getParsePlayUrlContent(); - - Assert.assertNotNull(deeplinkContent.getContent()); - Assert.assertNotNull(deeplinkContent.getHash()); - Assert.assertTrue(deeplinkContent.getContent().contains(deeplinkContent.getHash())); - } - - @Test - public void refreshParsePlayUrlContentTest() { - DeeplinkContent deeplinkContent = deeplinkService.refreshParsePlayUrlContent(); - - Assert.assertNotNull(deeplinkContent.getContent()); - Assert.assertNotNull(deeplinkContent.getHash()); - Assert.assertTrue(deeplinkContent.getContent().contains(deeplinkContent.getHash())); - Assert.assertFalse(deeplinkContent.getContent().contains("INJECT TVTOPICS OBJECT")); - Assert.assertFalse(deeplinkContent.getContent().contains("INJECT TVEVENTS OBJECT")); - } -} From 3da3f4a5799193202902a58ec245e7447476d172 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 20:42:35 +0200 Subject: [PATCH 25/45] Order by js version desc and count desc --- portal-app/src/app/deeplink/deeplink.component.html | 2 +- .../java/ch/srgssr/playfff/controller/DeepLinkController.java | 4 ++-- .../srgssr/playfff/repository/DeepLinkReportRepository.java | 2 +- .../java/ch/srgssr/playfff/service/DeepLinkReportService.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/portal-app/src/app/deeplink/deeplink.component.html b/portal-app/src/app/deeplink/deeplink.component.html index 991001d..b7dcf17 100644 --- a/portal-app/src/app/deeplink/deeplink.component.html +++ b/portal-app/src/app/deeplink/deeplink.component.html @@ -2,7 +2,7 @@

Deep link reports

-

Only failed url parsings are reported and ordered by count.

+

Only failed parsing urls are reported and ordered by JS version and count.

- +
diff --git a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java index 74b18dd..feba3f7 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java @@ -38,8 +38,8 @@ public ResponseEntity delete(@PathVariable("id") int id) { } @GetMapping("/api/v1/deeplink/report") - public ResponseEntity> findAllByOrderByCountDesc() { - return new ResponseEntity<>(deepLinkReportService.findAllByOrderByCountDesc(), HttpStatus.OK); + public ResponseEntity> findAll() { + return new ResponseEntity<>(deepLinkReportService.findAllByOrderByJsVersionDescCountDesc(), HttpStatus.OK); } // Public API diff --git a/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java index b087615..57f6dbb 100644 --- a/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java +++ b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java @@ -18,5 +18,5 @@ public interface DeepLinkReportRepository extends CrudRepository findAllByClientTimeLessThan(Date date); - List findAllByOrderByCountDesc(); + List findAllByOrderByJsVersionDescCountDesc(); } diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java index e6504e4..6c4dd73 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java @@ -69,7 +69,7 @@ public DeepLinkReport delete(long id) { return null; } - public Iterable findAllByOrderByCountDesc() { - return repository.findAllByOrderByCountDesc(); + public Iterable findAllByOrderByJsVersionDescCountDesc() { + return repository.findAllByOrderByJsVersionDescCountDesc(); } } From 1589d84174612654bd56d59f566f3a13656e8347 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 21:44:19 +0200 Subject: [PATCH 26/45] Renaming deep link --- src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java index b4379e9..c01dfb1 100644 --- a/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/DeeplinkConfig.java @@ -22,8 +22,8 @@ public class DeeplinkConfig { @Autowired private DeepLinkReportService deepLinkReportService; - @Scheduled(fixedDelayString = "${DEEPLINK_REFRESH_DELAY_MS:300000}") - public void DeeplinkRefresh() { + @Scheduled(fixedDelayString = "${DEEP_LINK_REFRESH_DELAY_MS:300000}") + public void DeepLinkRefresh() { deepLinkService.refreshParsePlayUrlJSContent(); deepLinkReportService.purgeOlderReports(); } From 3b648621e343a1b101f71479da72f595c7ca732c Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 21:44:31 +0200 Subject: [PATCH 27/45] Update documentation --- docs/README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index e470828..05966d5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ Playfff ## About -Playfff is a SRG micro service to serve extra datas to Play applications. Playfff means "Play Features and Functionalities with Flair". +Playfff is a SRG micro service to serve extra datas to Play applications. Playfff means "Play Features and Functionalities with Flair". ## Compatibility @@ -19,7 +19,8 @@ A wide list of parameters are available. * `PFFF_USER` (optional, string): A user login to admin service. * `PFFF_PASSWORD` (optional, string): A user password to admin service. -* `MAX_DEEP_LINK_REPORTS` (optional, integer): Maximum number of deep link reports in the database, default is `2500`. +* `DEEP_LINK_REFRESH_DELAY_MS` (optional, integer): Scheduled fixed delay before refreshing the deep link script cache. If not set, defaults is `300000`. +* `MAX_DEEP_LINK_REPORTS` (optional, integer): Maximum number of deep link reports in the database. If not set, defaults is `2500`. ## API * `urn` (string): an unique identifier. @@ -45,6 +46,15 @@ A wide list of parameters are available. * `/api/v1/whatisnew/text?package={package}&version={version}` : get WhatIsNewResult object. * `/api/v1/whatisnew/html?package={package}&version={version}` : get What's new html format. +#### Deep link + +* `/api/v1/deeplink/parse_play_url.js` (GET): Get the Play web URL to mobile application scheme URL script (deep link script). The HTTP ETag caching is supported. +* `/api/v1/deeplink/report` (POST) : create or update a new deep link report object from the JSON body object. Send a report only if the script returns `[scheme]://redirect`. The JSON object must contains: + * `clientTime` (string): date of the parsing execution in `yyyy-MM-dd'T'HH:mm:ssXXX` format. + * `clientId` (string): Bundle id or package name. + * `jsVersion` (string): the `parse_play_url.js` value of `parsePlayUrlVersion` variable. + * `url` (string): the unparsing url. + #### Recommendation for a media * `/api/v2/playlist/recommendation/continuousPlayback/{urn}` : get media list object. @@ -72,6 +82,12 @@ Private APIs need a user authentification. * `/api/v1/update` (PUT) : update an update object from the body object. * `/api/v1/update/{id}` (GET) : get update object with `id` identifier. * `/api/v1/update/{id}` (DELETE) : remove update object with `id` identifier. + +#### Deep link + +* `/api/v1/deeplink/report` (GET) : get All deep link reports. +* `/api/v1/deeplink/report/{id}` (GET) : get deep link report object with `id` identifier. +* `/api/v1/deeplink/report/{id}` (DELETE) : remove deep link report object with `id` identifier. ## License From 966dafe2def46767a03c8a264af2ac9bfee094a0 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 22:09:40 +0200 Subject: [PATCH 28/45] Use an integer as a JS script version --- docs/README.md | 2 +- .../playfff/controller/DeepLinkController.java | 2 +- .../ch/srgssr/playfff/model/DeepLinkReport.java | 2 +- .../repository/DeepLinkReportRepository.java | 2 +- .../playfff/service/DeepLinkReportService.java | 2 +- src/main/resources/parse_play_url.js | 2 +- .../controller/DeepLinkIntegrationTest.java | 14 +++++++------- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/README.md b/docs/README.md index 05966d5..3ed089d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,7 +52,7 @@ A wide list of parameters are available. * `/api/v1/deeplink/report` (POST) : create or update a new deep link report object from the JSON body object. Send a report only if the script returns `[scheme]://redirect`. The JSON object must contains: * `clientTime` (string): date of the parsing execution in `yyyy-MM-dd'T'HH:mm:ssXXX` format. * `clientId` (string): Bundle id or package name. - * `jsVersion` (string): the `parse_play_url.js` value of `parsePlayUrlVersion` variable. + * `jsVersion` (integer): the `parse_play_url.js` value of `parsePlayUrlVersion` variable. * `url` (string): the unparsing url. #### Recommendation for a media diff --git a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java index feba3f7..d76213f 100644 --- a/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java +++ b/src/main/java/ch/srgssr/playfff/controller/DeepLinkController.java @@ -48,7 +48,7 @@ public ResponseEntity create(@RequestBody DeepLinkReport deepLin if (deepLinkReport == null || deepLinkReport.clientTime == null || deepLinkReport.clientId == null - || deepLinkReport.jsVersion == null || deepLinkReport.url == null) { + || deepLinkReport.jsVersion == 0 || deepLinkReport.url == null) { return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); } else { return new ResponseEntity<>(deepLinkReportService.save(deepLinkReport), HttpStatus.CREATED); diff --git a/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java b/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java index a657e8e..09574eb 100644 --- a/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java +++ b/src/main/java/ch/srgssr/playfff/model/DeepLinkReport.java @@ -24,7 +24,7 @@ public class DeepLinkReport { public Date clientTime; public String clientId; - public String jsVersion; + public int jsVersion; @Column(length = 512) public String url; diff --git a/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java index 57f6dbb..3d7f4a0 100644 --- a/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java +++ b/src/main/java/ch/srgssr/playfff/repository/DeepLinkReportRepository.java @@ -12,7 +12,7 @@ * License information is available from the LICENSE file. */ public interface DeepLinkReportRepository extends CrudRepository { - DeepLinkReport findFirstByClientIdAndJsVersionAndUrl(String clientId, String jsVersion, String url); + DeepLinkReport findFirstByClientIdAndJsVersionAndUrl(String clientId, int jsVersion, String url); List findAllByOrderByClientTimeDesc(); diff --git a/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java index 6c4dd73..235308e 100644 --- a/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java +++ b/src/main/java/ch/srgssr/playfff/service/DeepLinkReportService.java @@ -51,7 +51,7 @@ public synchronized void purgeOlderReports() { } } - private DeepLinkReport getParsingReport(String clientId, String jsVersion, String url) { + private DeepLinkReport getParsingReport(String clientId, int jsVersion, String url) { return repository.findFirstByClientIdAndJsVersionAndUrl(clientId, jsVersion, url); } diff --git a/src/main/resources/parse_play_url.js b/src/main/resources/parse_play_url.js index a29f105..6b559e6 100644 --- a/src/main/resources/parse_play_url.js +++ b/src/main/resources/parse_play_url.js @@ -1,6 +1,6 @@ // parse_play_url -var parsePlayUrlVersion = "v1.0-dev14"; +var parsePlayUrlVersion = 14; var parsePlayUrlBuild = "mmf"; var parsePlayUrl = function(urlString) { diff --git a/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java b/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java index 433b209..5263059 100644 --- a/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java +++ b/src/test/java/ch/srgssr/playfff/controller/DeepLinkIntegrationTest.java @@ -80,7 +80,7 @@ public void reportSave() throws Exception { mvc.perform(post("/api/v1/deeplink/report").contentType(MediaType.APPLICATION_JSON).content(nonAcceptableJson1)).andExpect(status().isNotAcceptable()); DeepLinkReport nonAcceptableDeepLinkReport2 = new DeepLinkReport(); - nonAcceptableDeepLinkReport2.jsVersion = "v1.0"; + nonAcceptableDeepLinkReport2.jsVersion = 14; nonAcceptableDeepLinkReport2.url = "https://www.rts.ch/rts/play/unknown1"; String nonAcceptableJson2 = JsonUtil.getMapper().writeValueAsString(nonAcceptableDeepLinkReport2); @@ -88,7 +88,7 @@ public void reportSave() throws Exception { DeepLinkReport nonAcceptableDeepLinkReport3 = new DeepLinkReport(); nonAcceptableDeepLinkReport3.clientId = "ch.rts.rtsplayer"; - nonAcceptableDeepLinkReport3.jsVersion = "v1.0"; + nonAcceptableDeepLinkReport3.jsVersion = 14; nonAcceptableDeepLinkReport3.url = "https://www.rts.ch/rts/play/unknown1"; String nonAcceptableJson3 = JsonUtil.getMapper().writeValueAsString(nonAcceptableDeepLinkReport3); @@ -97,7 +97,7 @@ public void reportSave() throws Exception { DeepLinkReport acceptableDeepLinkReport1 = new DeepLinkReport(); acceptableDeepLinkReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); acceptableDeepLinkReport1.clientId = "ch.rts.rtsplayer"; - acceptableDeepLinkReport1.jsVersion = "v1.0"; + acceptableDeepLinkReport1.jsVersion = 14; acceptableDeepLinkReport1.url = "https://www.rts.ch/rts/play/unknown1"; String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport1); @@ -107,7 +107,7 @@ public void reportSave() throws Exception { DeepLinkReport acceptableDeepLinkReport1Bis = new DeepLinkReport(); acceptableDeepLinkReport1Bis.clientTime = dateFormat.parse("2019-08-20T16:15:53+02:00"); acceptableDeepLinkReport1Bis.clientId = "ch.rts.rtsplayer"; - acceptableDeepLinkReport1Bis.jsVersion = "v1.0"; + acceptableDeepLinkReport1Bis.jsVersion = 14; acceptableDeepLinkReport1Bis.url = "https://www.rts.ch/rts/play/unknown1"; String acceptableJson1bis = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport1Bis); @@ -119,7 +119,7 @@ public void reportSave() throws Exception { DeepLinkReport acceptableDeepLinkReport2 = new DeepLinkReport(); acceptableDeepLinkReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); acceptableDeepLinkReport2.clientId = "ch.rts.rtsplayer"; - acceptableDeepLinkReport2.jsVersion = "v1.0"; + acceptableDeepLinkReport2.jsVersion = 14; acceptableDeepLinkReport2.url = "https://www.rts.ch/rts/play/unknown2"; String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport2); @@ -135,7 +135,7 @@ public void reportChange() throws Exception { DeepLinkReport acceptableDeepLinkReport1 = new DeepLinkReport(); acceptableDeepLinkReport1.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); acceptableDeepLinkReport1.clientId = "ch.rts.rtsplayer"; - acceptableDeepLinkReport1.jsVersion = "v1.0"; + acceptableDeepLinkReport1.jsVersion = 14; acceptableDeepLinkReport1.url = "https://www.rts.ch/rts/play/unknown1"; String acceptableJson1 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport1); @@ -149,7 +149,7 @@ public void reportChange() throws Exception { DeepLinkReport acceptableDeepLinkReport2 = new DeepLinkReport(); acceptableDeepLinkReport2.clientTime = dateFormat.parse("2019-07-20T16:15:53+02:00"); acceptableDeepLinkReport2.clientId = "ch.rts.rtsplayer"; - acceptableDeepLinkReport2.jsVersion = "v1.0"; + acceptableDeepLinkReport2.jsVersion = 14; acceptableDeepLinkReport2.url = "https://www.rts.ch/rts/play/unknown2"; String acceptableJson2 = JsonUtil.getMapper().writeValueAsString(acceptableDeepLinkReport2); From 363fcaf14972462dd78a918832d6ddde7cca3d56 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 22:22:30 +0200 Subject: [PATCH 29/45] Use an integer as a JS script version in the web portal too --- portal-app/src/app/models/deeplink-report.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal-app/src/app/models/deeplink-report.model.ts b/portal-app/src/app/models/deeplink-report.model.ts index 436160f..b045ee5 100644 --- a/portal-app/src/app/models/deeplink-report.model.ts +++ b/portal-app/src/app/models/deeplink-report.model.ts @@ -3,6 +3,6 @@ export class DeeplinkReport { clientId: string; clientTime: string; count: number; - jsVersion: string; + jsVersion: number; url: string; } From 906cb854db75da15307141f8129ddd9721c61357 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 23:08:07 +0200 Subject: [PATCH 30/45] Add logout button in web portal --- portal-app/src/app/app.component.html | 6 ++++++ .../ch/srgssr/playfff/config/AuthenticationConfig.java | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/portal-app/src/app/app.component.html b/portal-app/src/app/app.component.html index 2dd87e0..377e750 100644 --- a/portal-app/src/app/app.component.html +++ b/portal-app/src/app/app.component.html @@ -6,6 +6,12 @@

Pfff - Admin

+

+ +

+
Updates Deep link diff --git a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java index 9c38e79..255907b 100644 --- a/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java +++ b/src/main/java/ch/srgssr/playfff/config/AuthenticationConfig.java @@ -13,6 +13,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.Collections; @@ -50,9 +51,16 @@ protected void configure(HttpSecurity http) throws Exception { .and() .logout() .deleteCookies("JSESSIONID") + .invalidateHttpSession(true) + .logoutUrl("/logout") + .logoutSuccessUrl("/login") .permitAll(); http .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); + + http + .logout() + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")); } @Override From 2e9761be3f9d387a4405c72ee33f3823560fed2d Mon Sep 17 00:00:00 2001 From: Pierre-Yves Bertholon Date: Sat, 20 Jul 2019 23:37:13 +0200 Subject: [PATCH 31/45] Set 404 page in english --- src/main/resources/templates/error.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 2fc03bd..5350d62 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -2,7 +2,7 @@ - Erreur + Error