diff --git a/docs/README.md b/docs/README.md index f1aa8a1..059c5ff 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,7 +24,8 @@ A wide list of parameters are available. * `urn` (string): an unique identifier. * `recommendedList` (object): a recommended result list with proterties: * `recommendationId` (string): the recommendation identifer from the service. - * `urns`(array): array of `urn`. + * `urns` (array): array of `urn`. + * `title` (string, optional): title of the playlist. * `package` (string): Android package name or iOS bundle identifier. * `version` (string): mobile application version. @@ -43,16 +44,22 @@ 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. -#### Recommendation +#### Recommendation for a media * `/api/v2/playlist/recommendation/continuousPlayback/{urn}` : get media list object. * `standalone` (optional, boolean): Recommendation for the playback mode. Default is `false`. * Returns a `recommendedList` object. -* `/api/v1/playlist/recommendation/continuousPlayback/{urn}` : get media list object. +* *Deprecated* `/api/v1/playlist/recommendation/continuousPlayback/{urn}` : get media list object. * `standalone` (optional, boolean): Recommendation for the playback mode. Default is `false`. * `format` (optional, string): If set to `urn`, it returns an URN list. Default is `media` and redirects to an IL media list response. +#### Personnal recommendation for a user + +* `/api/v2/playlist/recommendation/personalRecommendation` : get media list object. + * `user` (optional, string): `UserId` to use for a personal recommendation. + * Returns a `recommendedList` object. + ## Private APIs Private APIs need a user authentification. diff --git a/pom.xml b/pom.xml index c5cdd1d..eaf286f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,58 +1,58 @@ - 4.0.0 - - - - Pfff repository - https://raw.github.com/SRGSSR/pfff/mvn-repo/ - - - - com.example - pfff - 9 - jar - - pfff - Play Features and Functionalities with Flair - - - org.springframework.boot - spring-boot-starter-parent - 1.5.9.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.postgresql - postgresql - 9.4-1201-jdbc4 - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + + + Pfff repository + https://raw.github.com/SRGSSR/pfff/mvn-repo/ + + + + com.example + pfff + 9 + jar + + pfff + Play Features and Functionalities with Flair + + + org.springframework.boot + spring-boot-starter-parent + 1.5.9.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.postgresql + postgresql + 9.4-1201-jdbc4 + com.h2database @@ -64,117 +64,111 @@ spring-boot-starter-thymeleaf - - org.springframework.boot - spring-boot-starter-security - - - - org.springframework.security - spring-security-test - test - - - - org.webjars - jquery - 3.3.1 - - - org.webjars - bootstrap - 3.3.7 - - - org.webjars - webjars-locator - - - org.webjars - font-awesome - 5.4.1 - - - org.assertj - assertj-core - - - ch.srf.integrationlayer - integrationlayer-domain-objects - 1.20.272 - - - - com.google.guava - guava - 27.0.1-jre - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - maven-clean-plugin - 2.5 - - - - src/main/resources/static - - * - - - - - - - - com.github.eirslett - frontend-maven-plugin - 1.6 - - portal-app - - - - install node and npm - - install-node-and-npm - - - v6.9.5 - 3.10.10 - - - - - npm install - - npm - - - install - - - - - prod - - npm - - - run-script prod - - generate-resources - - - - - + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.security + spring-security-test + test + + + + org.webjars + jquery + 3.3.1 + + + org.webjars + bootstrap + 3.3.7 + + + org.webjars + webjars-locator + + + org.webjars + font-awesome + 5.4.1 + + + org.assertj + assertj-core + + + ch.srf.integrationlayer + integrationlayer-domain-objects + 1.20.272 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + maven-clean-plugin + 2.5 + + + + src/main/resources/static + + * + + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.6 + + portal-app + + + + install node and npm + + install-node-and-npm + + + v6.9.5 + 3.10.10 + + + + + npm install + + npm + + + install + + + + + prod + + npm + + + run-script prod + + generate-resources + + + + + diff --git a/src/main/java/com/example/pfff/config/AuthenticationConfig.java b/src/main/java/com/example/pfff/config/AuthenticationConfig.java index 3134cdf..a1dbad3 100644 --- a/src/main/java/com/example/pfff/config/AuthenticationConfig.java +++ b/src/main/java/com/example/pfff/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/recommendation/**", "/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/**").permitAll() .anyRequest().authenticated() .and() .formLogin() diff --git a/src/main/java/com/example/pfff/controller/RecommendationController.java b/src/main/java/com/example/pfff/controller/RecommendationController.java index 3d1ca99..58cefa7 100644 --- a/src/main/java/com/example/pfff/controller/RecommendationController.java +++ b/src/main/java/com/example/pfff/controller/RecommendationController.java @@ -4,10 +4,7 @@ import com.example.pfff.service.RecommendationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.util.UriComponentsBuilder; @@ -24,6 +21,7 @@ public class RecommendationController { @Autowired RecommendationService service; + @Deprecated @RequestMapping("/api/v1/playlist/recommendation/{purpose}/{urn}") @ResponseBody Object recommendationV1( @@ -47,7 +45,7 @@ Object recommendationV1( @RequestMapping("/api/v2/playlist/recommendation/{purpose}/{urn}") @ResponseBody - Object recommendationV2( + RecommendedList recommendationV2( HttpServletRequest request, @PathVariable("purpose") String purpose, @PathVariable("urn") String urn, @@ -60,4 +58,13 @@ private RecommendedList getRecommendationList(@PathVariable("purpose") String pu recommendedList.addUrn(0, urn); return recommendedList; } + + @RequestMapping("/api/v2/playlist/personalRecommendation") + @ResponseBody + RecommendedList personalRecommendation( + HttpServletRequest request, + @RequestParam(value = "user", required = false, defaultValue = "unknown") String userId) { + return service.rtsPlayHomePersonalRecommendation(userId); + } + } diff --git a/src/main/java/com/example/pfff/model/RecommendedList.java b/src/main/java/com/example/pfff/model/RecommendedList.java index 2578ba9..e34a100 100644 --- a/src/main/java/com/example/pfff/model/RecommendedList.java +++ b/src/main/java/com/example/pfff/model/RecommendedList.java @@ -17,6 +17,12 @@ public class RecommendedList { private String recommendationId; private List urns; + private String title; + + public RecommendedList(String title, String host, String recommendationId, List urns) { + this(host, recommendationId, urns); + this.title = title; + } public RecommendedList(String host, String recommendationId, List urns) { if (host != null && recommendationId != null) { @@ -42,6 +48,10 @@ public List getUrns() { return urns; } + public String getTitle() { + return title; + } + public void addUrn(int index, String urn) { this.urns.add(index, urn); } diff --git a/src/main/java/com/example/pfff/model/peach/PersonalRecommendationResult.java b/src/main/java/com/example/pfff/model/peach/PersonalRecommendationResult.java new file mode 100644 index 0000000..d9ba56c --- /dev/null +++ b/src/main/java/com/example/pfff/model/peach/PersonalRecommendationResult.java @@ -0,0 +1,56 @@ +package com.example.pfff.model.peach; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PersonalRecommendationResult { + public String status; + public String codops; + public Result result; + + public static class Result { + + @JsonProperty("fallback_used") + public boolean fallbackUsed; + public String title; + public List items; + public String id; + } + public static class Item { + + public String urn; + //TODO Implement once explanation semantics has been implemented in peach backend + public Explanation explanation; + + } + /** + * Explanation has been defined in Jan. 2019 but not yet used. + */ + public static class Explanation { + + public String text; + @JsonProperty("media_reference_urn") + public String mediaReferenceUrn; + } + + public String getTitle() { + return result != null ? result.title : null; + } + + public String getRecommendationId() { + return result == null ? null : result.id; + } + + public List getUrns() { + if (result != null && result.items != null) { + return result.items.stream().map((i) -> i.urn).collect(Collectors.toList()); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/pfff/service/RecommendationService.java b/src/main/java/com/example/pfff/service/RecommendationService.java index b0dfb34..868dd94 100644 --- a/src/main/java/com/example/pfff/service/RecommendationService.java +++ b/src/main/java/com/example/pfff/service/RecommendationService.java @@ -5,9 +5,9 @@ import ch.srg.il.domain.v2_0.Media; import ch.srg.il.domain.v2_0.MediaType; import com.example.pfff.model.Environment; +import com.example.pfff.model.peach.PersonalRecommendationResult; import com.example.pfff.model.RecommendedList; import com.example.pfff.model.peach.RecommendationResult; -import com.google.common.collect.Lists; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; @@ -16,6 +16,7 @@ import org.springframework.web.util.UriComponentsBuilder; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -60,7 +61,8 @@ private RecommendedList rtsAudioRecommendedList(String urn) { return new RecommendedList(); } - List episodes = Lists.reverse(episodeComposition.getList()); + List episodes = new ArrayList<>(episodeComposition.getList()); + Collections.reverse(episodes); List fullLengthUrns = episodes.stream().map(EpisodeWithMedias::getFullLengthUrn).collect(Collectors.toList()); List clipUrns = episodes.stream().flatMap(e -> e.getMediaList().stream().filter(m -> m.getMediaType() == MediaType.AUDIO)).map(Media::getUrn).collect(Collectors.toList()); clipUrns.removeAll(fullLengthUrns); @@ -97,7 +99,8 @@ private RecommendedList rtsAudioRecommendedList(String urn) { urns.remove(urn); if (episodeComposition.getNext() != null) { - recommendationResult.addAll(Lists.reverse(urns)); + Collections.reverse(urns); + recommendationResult.addAll(urns); } else { recommendationResult.addAll(urns); } @@ -128,4 +131,18 @@ private RecommendedList rtsVideoRecommendedList(String purpose, String urn, bool return new RecommendedList(url.getHost(), recommendationResult.getRecommendationId(), recommendationResult.getUrns()); } + + public RecommendedList rtsPlayHomePersonalRecommendation(String userId) { + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.newInstance().scheme("http") + .host("peach.ebu.io").path("api/v1/chrts/play_home_personal_rec"); + uriComponentsBuilder.queryParam("user_id", userId); + UriComponents url = uriComponentsBuilder.build(); + + System.out.println(url.toUriString()); + + PersonalRecommendationResult result = restTemplate + .exchange(url.toUriString(), HttpMethod.GET, null, PersonalRecommendationResult.class).getBody(); + + return new RecommendedList(result.getTitle(), url.getHost(), result.getRecommendationId(), result.getUrns()); + } } \ No newline at end of file diff --git a/src/test/java/com/example/pfff/controller/RecommendationServiceTests.java b/src/test/java/com/example/pfff/controller/RecommendationServiceTests.java index a6974b6..73c45eb 100644 --- a/src/test/java/com/example/pfff/controller/RecommendationServiceTests.java +++ b/src/test/java/com/example/pfff/controller/RecommendationServiceTests.java @@ -9,8 +9,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import java.util.List; - @RunWith(SpringRunner.class) @SpringBootTest public class RecommendationServiceTests { @@ -27,9 +25,7 @@ public void getRecommendedUrnsContinuousplaybackRTSVideoTest() { Assert.assertNotNull(recommendedList.getRecommendationId()); Assert.assertTrue(recommendedList.getRecommendationId().startsWith("io.ebu.peach:")); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertTrue(recommendedList.getUrns().size() > 0); - Assert.assertTrue(recommendedList.getUrns().size() < 50); + assertValidList(recommendedList); } @Test @@ -41,9 +37,7 @@ public void getRecommendedUrnsContinuousplaybackStandaloneRTSVideoTest() { Assert.assertNotNull(recommendedList.getRecommendationId()); Assert.assertTrue(recommendedList.getRecommendationId().startsWith("io.ebu.peach:")); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertTrue(recommendedList.getUrns().size() > 0); - Assert.assertTrue(recommendedList.getUrns().size() < 50); + assertValidList(recommendedList); } @Test @@ -57,9 +51,7 @@ public void getRecommendedUrnsContinuousplaybackRTSAudioFullTest() { Assert.assertNotNull(recommendedList.getRecommendationId()); Assert.assertTrue(recommendedList.getRecommendationId().startsWith("ch.srgssr.playfff:EpisodeComposition/LatestByShow/" + showURN + "/FullLength/")); Assert.assertTrue(recommendedList.getRecommendationId().contains(mediaURN)); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertTrue(recommendedList.getUrns().size() > 0); - Assert.assertTrue(recommendedList.getUrns().size() < 50); + assertValidList(recommendedList); } @Test @@ -73,9 +65,7 @@ public void getRecommendedUrnsContinuousplaybackStandaloneRTSAudioFullTest() { Assert.assertNotNull(recommendedList.getRecommendationId()); Assert.assertTrue(recommendedList.getRecommendationId().startsWith("ch.srgssr.playfff:EpisodeComposition/LatestByShow/" + showURN + "/FullLength/")); Assert.assertTrue(recommendedList.getRecommendationId().contains(mediaURN)); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertTrue(recommendedList.getUrns().size() > 0); - Assert.assertTrue(recommendedList.getUrns().size() < 50); + assertValidList(recommendedList); } @Test @@ -89,9 +79,7 @@ public void getRecommendedUrnsContinuousplaybackRTSAudioClipTest() { Assert.assertNotNull(recommendedList.getRecommendationId()); Assert.assertTrue(recommendedList.getRecommendationId().startsWith("ch.srgssr.playfff:EpisodeComposition/LatestByShow/" + showURN + "/Clip/")); Assert.assertTrue(recommendedList.getRecommendationId().contains(mediaURN)); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertTrue(recommendedList.getUrns().size() > 0); - Assert.assertTrue(recommendedList.getUrns().size() < 50); + assertValidList(recommendedList); } @Test @@ -105,9 +93,7 @@ public void getRecommendedUrnsContinuousplaybackStandaloneRTSAudioClipTest() { Assert.assertNotNull(recommendedList.getRecommendationId()); Assert.assertTrue(recommendedList.getRecommendationId().startsWith("ch.srgssr.playfff:EpisodeComposition/LatestByShow/" + showURN + "/Clip/")); Assert.assertTrue(recommendedList.getRecommendationId().contains(mediaURN)); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertTrue(recommendedList.getUrns().size() > 0); - Assert.assertTrue(recommendedList.getUrns().size() < 50); + assertValidList(recommendedList); } @Test @@ -117,9 +103,7 @@ public void getRecommendedUrnsContinuousplaybackSRFTest() { boolean standalone = false; RecommendedList recommendedList = recommendationService.getRecommendedUrns(purpose, mediaURN, standalone); - Assert.assertNull(recommendedList.getRecommendationId()); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertEquals(recommendedList.getUrns().size(), 0); + assertInvalidList(recommendedList); } @Test @@ -129,9 +113,7 @@ public void getRecommendedUrnsContinuousplaybackStandaloneSRFTest() { boolean standalone = true; RecommendedList recommendedList = recommendationService.getRecommendedUrns(purpose, mediaURN, standalone); - Assert.assertNull(recommendedList.getRecommendationId()); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertEquals(recommendedList.getUrns().size(), 0); + assertInvalidList(recommendedList); } @Test @@ -141,9 +123,7 @@ public void getRecommendedUrnsContinuousplaybackSwissTxtTest() { boolean standalone = false; RecommendedList recommendedList = recommendationService.getRecommendedUrns(purpose, mediaURN, standalone); - Assert.assertNull(recommendedList.getRecommendationId()); - Assert.assertNotNull(recommendedList.getUrns()); - Assert.assertEquals(recommendedList.getUrns().size(), 0); + assertInvalidList(recommendedList); } @Test @@ -153,6 +133,36 @@ public void getRecommendedUrnsContinuousplaybackStandaloneSwisstxtTest() { boolean standalone = true; RecommendedList recommendedList = recommendationService.getRecommendedUrns(purpose, mediaURN, standalone); + assertInvalidList(recommendedList); + } + + @Test + public void playHomeTestInvalidUser() { + RecommendedList recommendedList = recommendationService.rtsPlayHomePersonalRecommendation("invalid user"); + + assertValidList(recommendedList); + } + + @Test + public void playHomeTestUserUnknown() { + RecommendedList recommendedList = recommendationService.rtsPlayHomePersonalRecommendation("unknown"); + + assertValidList(recommendedList); + } + + @Test + public void playHomeTestUser9() { + RecommendedList recommendedList = recommendationService.rtsPlayHomePersonalRecommendation("9"); + + assertValidList(recommendedList); + } + + private void assertValidList(RecommendedList recommendedList) { + Assert.assertTrue(recommendedList.getUrns().size() > 0); + Assert.assertTrue(recommendedList.getUrns().size() < 50); + } + + private void assertInvalidList(RecommendedList recommendedList) { Assert.assertNull(recommendedList.getRecommendationId()); Assert.assertNotNull(recommendedList.getUrns()); Assert.assertEquals(recommendedList.getUrns().size(), 0);