From ba7b0779bd11f13b9420d981bc0f014b57ea6639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Andrzejak?= Date: Tue, 29 Aug 2023 15:02:39 +0200 Subject: [PATCH] DCB-130 Extend Application metadata with preferred field --- LICENSE-THIRD-PARTY.txt | 2 +- README.md | 1 + appstore-metadata-service-tests/README.md | 3 + .../functional/MaintainerApiFTSpec.groovy | 41 ++++++---- .../test/cases/functional/StbApiFTSpec.groovy | 19 ++--- .../real/sanity/DevApiFTSpecSanity.groovy | 8 +- .../request/ApplicationMetadataBuilder.java | 13 ++++ .../response/ApplicationDetailsPath.java | 8 ++ .../api/maintainer/PersistentAppsService.java | 68 +++++++++++++--- .../MaintainerApplicationHeaderMapper.java | 1 + ...intainerSingleApplicationHeaderMapper.java | 1 + .../api/stb/PersistentAppsService.java | 42 ++++++---- .../util/ApplicationPreferredHelper.java | 78 +++++++++++++++++++ .../migration/V05__Add_preferred_column.sql | 20 +++++ .../static/appstore-metadata-service.yaml | 12 ++- .../MaintainerAppsControllerTest.java | 10 ++- .../maintainer/PersistentAppsServiceTest.java | 4 + .../metadata/api/service/BaseServiceTest.java | 1 + charts/index.yaml | 19 +++++ 19 files changed, 296 insertions(+), 55 deletions(-) create mode 100644 appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/util/ApplicationPreferredHelper.java create mode 100644 appstore-metadata-service/src/main/resources/db/migration/V05__Add_preferred_column.sql diff --git a/LICENSE-THIRD-PARTY.txt b/LICENSE-THIRD-PARTY.txt index 1e929d7..5e203ff 100644 --- a/LICENSE-THIRD-PARTY.txt +++ b/LICENSE-THIRD-PARTY.txt @@ -17,7 +17,7 @@ Lists of 149 third-party dependencies. (The Apache Software License, Version 2.0) Java Faker (com.github.javafaker:javafaker:1.0.2 - http://github.com/DiUS/java-faker) (The Apache Software License, Version 2.0) Generex (com.github.mifmif:generex:1.0.2 - https://github.com/mifmif/Generex/tree/master) (The Apache Software License, Version 2.0) project ':json-path' (com.jayway.jsonpath:json-path:2.6.0 - https://github.com/jayway/JsonPath) - (Apache License, Version 2.0) appstore-metadata-service (com.lgi.appstore.metadata:appstore-metadata-service:0.1.10-SNAPSHOT - https://spring.io/projects/spring-boot/appstore-metadata-service-parent/appstore-metadata-service) + (Apache License, Version 2.0) appstore-metadata-service (com.lgi.appstore.metadata:appstore-metadata-service:0.1.11-SNAPSHOT - https://spring.io/projects/spring-boot/appstore-metadata-service-parent/appstore-metadata-service) (Eclipse Distribution License - v 1.0) Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:2.3.3 - https://eclipse-ee4j.github.io/jaxb-ri/jaxb-bundles/jaxb-impl) (Apache License 2.0) JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk) (The Apache Software License, Version 2.0) HikariCP (com.zaxxer:HikariCP:4.0.3 - https://github.com/brettwooldridge/HikariCP) diff --git a/README.md b/README.md index cc1fcad..71e771b 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ curl --location --request POST 'http://localhost:8081/as3/maintainers/lgi/apps' "url": "http://url/fancyappl", "visible": true, "encryption": false, + "preferred": false, "ociImageUrl": "myregistry.local:5000/testing/test-image", "latest" : true, "type": "fancy_applications", diff --git a/appstore-metadata-service-tests/README.md b/appstore-metadata-service-tests/README.md index cf695c3..5357270 100644 --- a/appstore-metadata-service-tests/README.md +++ b/appstore-metadata-service-tests/README.md @@ -139,6 +139,7 @@ curl -v -X POST -H "Content-type: application/json" -d '{ "version": "1.2.3", "visible": true, "encryption": false, + "preferred": false, "ociImageUrl": "myregistry.local:5000/testing/test-image" }, "requirements": { @@ -182,6 +183,7 @@ curl -v -X POST -H "Content-type: application/json" -d '{ "version": "1.2.3", "visible": true, "encryption": false, + "preferred": false, "ociImageUrl": "myregistry.local:5000/testing/test-image" }, "requirements": { @@ -219,6 +221,7 @@ curl -v -X POST -H "Content-type: application/json" -d '{ "version": "0.0.1", "visible": true, "encryption": false, + "preferred": false, "ociImageUrl": "myregistry.local:5000/testing/test-image" }, "requirements": { diff --git a/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/MaintainerApiFTSpec.groovy b/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/MaintainerApiFTSpec.groovy index 6adc82f..dd2fb5e 100644 --- a/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/MaintainerApiFTSpec.groovy +++ b/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/MaintainerApiFTSpec.groovy @@ -49,6 +49,7 @@ import static com.lgi.appstore.metadata.test.framework.model.response.Applicatio import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_ICON import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_NAME import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_OCI_IMAGE_URL +import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_PREFERRED import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_TYPE import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_SIZE import static com.lgi.appstore.metadata.test.framework.model.response.ApplicationDetailsPath.FIELD_URL @@ -127,7 +128,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { def "create non-existing app and view details for #behavior"() { given: "developer create 2 applications: first with 2 versions (incl. hidden latest) and second with only one version" Application app1v1 = builder().fromDefaults() - .withId(appId).withVersion(v1).forCreate() + .withId(appId).withVersion(v1).withPreferred(isV1Preferred).forCreate() Application app1v2 = builder().fromDefaults() .withId(appId).withVersion(v2).withVisible(isV2Visible).forCreate() Application app2v1 = builder().fromDefaults() @@ -151,13 +152,14 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { v2 == returnedV ? field().header().visible().from(jsonBody) == isV2Visible : IGNORE_THIS_ASSERTION where: - behavior | appId | v1 | v2 | isV2Visible | queryAppKey || httpStatus | returnedV - "no version specified - fallback to highest v" | randId() | "0.10.0" | "1.1.0" | true | appId || SC_OK | v2 - "accepting 'latest' keyword" | randId() | "1.0.0" | "0.10.0" | true | appId + ":latest" || SC_OK | v1 - "query for specific version" | randId() | "0.1.0" | "1.0.0" | true | appId + ":" + v1 || SC_OK | v1 - "fallback to latest that is hidden" | randId() | "1.0.0" | "2.0.0" | false | appId || SC_OK | v2 - "not existing id" | randId() | "10.0.0" | "0.1.0" | true | "App3" || SC_NOT_FOUND | _ - "not existing version" | randId() | "10.0.0" | "0.1.0" | true | appId + ":3.0" || SC_NOT_FOUND | _ + behavior | appId | v1 | isV1Preferred | v2 | isV2Visible | queryAppKey || httpStatus | returnedV + "no version specified - fallback to highest v" | randId() | "0.10.0" | false | "1.1.0" | true | appId || SC_OK | v2 + "accepting 'latest' keyword" | randId() | "1.0.0" | false | "0.10.0" | true | appId + ":latest" || SC_OK | v1 + "query for specific version" | randId() | "0.1.0" | false | "1.0.0" | true | appId + ":" + v1 || SC_OK | v1 + "fallback to latest that is hidden" | randId() | "1.0.0" | false | "2.0.0" | false | appId || SC_OK | v2 + "not existing id" | randId() | "10.0.0" | false | "0.1.0" | true | "App3" || SC_NOT_FOUND | _ + "not existing version" | randId() | "10.0.0" | false | "0.1.0" | true | appId + ":3.0" || SC_NOT_FOUND | _ + "fallback preferred version v1" | randId() | "0.10.0" | true | "1.1.0" | true | appId || SC_OK | v1 } def "developer cannot access other developer application (GET/PUT/DELETE)"() { @@ -211,6 +213,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { and: "application has v1 with some metadata" def v1Visible = false def v1Encryption = false + def v1Preferred = false def v1OciImageUrl = "v1OciImageUrl" def v1Name = "v1Name" def v1Description = "v1Description" @@ -240,6 +243,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { .withVersion(v1) .withVisible(v1Visible) .withEncryption(v1Encryption) + .withPreferred(v1Preferred) .withOciImageUrl(v1OciImageUrl) .withName(v1Name) .withDescription(v1Description) @@ -258,6 +262,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { and: "application has v2 with completely different metadata" def v2Visible = true def v2Encryption = true + def v2Preferred = true def v2OciImageUrl = "v2OciImageUrl" def v2Name = "v2NewName" def v2Description = "v2NewDescription" @@ -286,6 +291,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { .withVersion(v2) .withVisible(v2Visible) .withEncryption(v2Encryption) + .withPreferred(v2Preferred) .withOciImageUrl(v2OciImageUrl) .withName(v2Name) .withDescription(v2Description) @@ -318,6 +324,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { field().header().version().from(theBody1) == v1 field().header().visible().from(theBody1) == v1Visible field().header().encryption().from(theBody1) == v1Encryption + field().header().preferred().from(theBody1) == v1Preferred field().header().ociImageUrl().from(theBody1) == v1OciImageUrl field().header().name().from(theBody1) == v1Name field().header().category().from(theBody1) == String.valueOf(v1Category) @@ -376,6 +383,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { field().header().version().from(theBody2) == v2 field().header().visible().from(theBody2) == v2Visible field().header().encryption().from(theBody2) == v2Encryption + field().header().preferred().from(theBody2) == v2Preferred field().header().ociImageUrl().from(theBody2) == v2OciImageUrl field().header().category().from(theBody2) == String.valueOf(v2Category) field().header().name().from(theBody2) == v2Name @@ -446,6 +454,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { field | valueBefore || valueAfter FIELD_VISIBLE | Boolean.FALSE || Boolean.TRUE FIELD_ENCRYPTION | Boolean.FALSE || Boolean.TRUE + FIELD_PREFERRED | Boolean.FALSE || Boolean.TRUE FIELD_NAME | "appNameBefore" || "appNameAfter" FIELD_DESCRIPTION | "Description Before ąćęłóśżź" || "Description After €\\€\\€\\€\\" FIELD_CATEGORY | String.valueOf(Category.DEV) || String.valueOf(pickRandomCategory()) @@ -507,6 +516,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { field | valueV1Before || valueV1After // must be different than the defaults FIELD_VISIBLE | Boolean.TRUE || Boolean.FALSE FIELD_ENCRYPTION | Boolean.FALSE || Boolean.TRUE + FIELD_PREFERRED | Boolean.FALSE || Boolean.TRUE FIELD_NAME | "appNameBefore" || "appNameAfter" FIELD_DESCRIPTION | "Description Before ąćęłóśżź" || "Description After €\\€\\€\\€\\" FIELD_CATEGORY | String.valueOf(Category.DEV) || String.valueOf(Category.RESOURCE) @@ -667,7 +677,7 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { dbSteps.listMaintainers() Application app1v1 = builder().fromDefaults() - .withId(id1).withVersion(v1).forCreate() + .withId(id1).withVersion(v1).withPreferred(isV1Preferred).forCreate() Application app1v2 = builder().fromDefaults() .withId(id1).withVersion(v2).forCreate() Application app2v1 = builder().fromDefaults() @@ -706,12 +716,13 @@ class MaintainerApiFTSpec extends AsmsFeatureSpecBase { } where: - dev2Code | id1 | id2 | id3 | limit | offset | v1 | v2 | v3 | queryDevCode || possibleIds | possibleV | count | total | returnedLimit - "lgi2" | randId() | randId() | randId() | 3 | 0 | "0.0.11" | "0.1.0" | "1.1.0" | DEFAULT_DEV_CODE || [id1, id2] | [v1, v2] | 2 | 2 | limit - "lgi2" | randId() | randId() | randId() | null | 0 | "0.1.1" | "0.0.1" | "1.0.1" | dev2Code || [id3] | [v3] | 1 | 1 | DEFAULT_LIMIT - "lgi2" | randId() | randId() | randId() | 1 | 0 | "0.11.1" | "1.0.1" | "2.0.1" | DEFAULT_DEV_CODE || [id1, id2] | [v1, v2] | 1 | 2 | limit - "lgi2" | randId() | randId() | randId() | 1 | 1 | "0.1.1" | "0.0.1" | "1.0.1" | DEFAULT_DEV_CODE || [id1, id2] | [v1, v2] | 1 | 2 | limit - "lgi2" | randId() | randId() | randId() | 1 | 2 | "0.1.1" | "0.0.1" | "1.0.1" | DEFAULT_DEV_CODE || _ | _ | 0 | 2 | limit + dev2Code | id1 | id2 | id3 | limit | offset | v1 | v2 | v3 | isV1Preferred || queryDevCode || possibleIds | possibleV | count | total | returnedLimit + "lgi2" | randId() | randId() | randId() | 3 | 0 | "0.0.11" | "0.1.0" | "1.1.0" | false || DEFAULT_DEV_CODE || [id1, id2] | [v1, v2] | 2 | 2 | limit + "lgi2" | randId() | randId() | randId() | null | 0 | "0.1.1" | "0.0.1" | "1.0.1" | false || dev2Code || [id3] | [v3] | 1 | 1 | DEFAULT_LIMIT + "lgi2" | randId() | randId() | randId() | 1 | 0 | "0.11.1" | "1.0.1" | "2.0.1" | false || DEFAULT_DEV_CODE || [id1, id2] | [v1, v2] | 1 | 2 | limit + "lgi2" | randId() | randId() | randId() | 1 | 1 | "0.1.1" | "0.0.1" | "1.0.1" | false || DEFAULT_DEV_CODE || [id1, id2] | [v1, v2] | 1 | 2 | limit + "lgi2" | randId() | randId() | randId() | 1 | 2 | "0.1.1" | "0.0.1" | "1.0.1" | false || DEFAULT_DEV_CODE || _ | _ | 0 | 2 | limit + "lgi2" | randId() | randId() | randId() | null | 0 | "0.1.1" | "1.0.1" | "0.0.1" | true || DEFAULT_DEV_CODE || [id1, id2] | [v1] | 2 | 3 | DEFAULT_LIMIT } @Unroll diff --git a/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/StbApiFTSpec.groovy b/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/StbApiFTSpec.groovy index 3d9cf35..5565d6b 100644 --- a/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/StbApiFTSpec.groovy +++ b/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/functional/StbApiFTSpec.groovy @@ -208,7 +208,7 @@ class StbApiFTSpec extends AsmsFeatureSpecBase { .withId(appId).withVersion(v1).withType("application/vnd.rdk-app.dac.native").forCreate() Application app1v2 = builder().fromDefaults() .withId(appId).withVersion(v2).withType("application/vnd.rdk-app.dac.native") - .withVisible(isV2Visible).forCreate() + .withVisible(isV2Visible).withPreferred(isV2Preferred).forCreate() Application app2v1 = builder().fromDefaults().withType("application/vnd.rdk-app.dac.native") .withId("someOther_$appId").withVersion(v1) forCreate() @@ -230,14 +230,15 @@ class StbApiFTSpec extends AsmsFeatureSpecBase { receivedStatus == SC_OK ? !field().header().visible().isPresentIn(jsonBody) : IGNORE_THIS_ASSERTION where: - behavior | appId | v1 | v2 | isV2Visible | queryAppKey || httpStatus | returnedV - "no version specified - fallback to highest v" | randId() | "1.0.0" | "0.10.0" | true | appId || SC_OK | v1 - "accepting 'latest' keyword" | randId() | "0.0.10" | "0.1.0" | true | appId + ":latest" || SC_OK | v2 - "query for specific version" | randId() | "0.1.0" | "1.0.0" | true | appId + ":" + v1 || SC_OK | v1 - "no fallback to latest that is hidden" | randId() | "1.0.0" | "2.0.0" | false | appId || SC_OK | v1 // hidden version is not taken into the account for STB - "not existing id" | randId() | "10.0.0" | "0.1.0" | true | "App3" || SC_NOT_FOUND | _ - "not existing version" | randId() | "10.0.0" | "20.0.0" | true | appId + ":3.0" || SC_NOT_FOUND | _ - "query for specific version that is hidden" | randId() | "0.1.0" | "1.0.0" | false | appId + ":" + v2 || SC_NOT_FOUND | _ // STB cannot get details of hidden version + behavior | appId | v1 | v2 | isV2Preferred | isV2Visible | queryAppKey || httpStatus | returnedV + "no version specified - fallback to highest v" | randId() | "1.0.0" | "0.10.0" | false | true | appId || SC_OK | v1 + "accepting 'latest' keyword" | randId() | "0.0.10" | "0.1.0" | false | true | appId + ":latest" || SC_OK | v2 + "query for specific version" | randId() | "0.1.0" | "1.0.0" | false | true | appId + ":" + v1 || SC_OK | v1 + "no fallback to latest that is hidden" | randId() | "1.0.0" | "2.0.0" | false | false | appId || SC_OK | v1 // hidden version is not taken into the account for STB + "not existing id" | randId() | "10.0.0" | "0.1.0" | false | true | "App3" || SC_NOT_FOUND | _ + "not existing version" | randId() | "10.0.0" | "20.0.0" | false | true | appId + ":3.0" || SC_NOT_FOUND | _ + "query for specific version that is hidden" | randId() | "0.1.0" | "1.0.0" | false | false | appId + ":" + v2 || SC_NOT_FOUND | _ // STB cannot get details of hidden version + "fallback preferred version v2" | randId() | "1.0.0" | "0.10.0" | true | true | appId || SC_OK | v2 } def "details of each version contain separate information about app requirements, maintainer and all visible versions of the application"() { diff --git a/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/real/sanity/DevApiFTSpecSanity.groovy b/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/real/sanity/DevApiFTSpecSanity.groovy index 764ebe0..a11b314 100644 --- a/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/real/sanity/DevApiFTSpecSanity.groovy +++ b/appstore-metadata-service-tests/src/test/groovy/com/lgi/appstore/metadata/test/cases/real/sanity/DevApiFTSpecSanity.groovy @@ -51,6 +51,7 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { def v1 = "1.0.1" def v1Visible = false def v1Encryption = false + def v1Preferred = false def v1OciImageUrl = "myregistry.local:5000/testing/test-image" def v1Name = "v1Name" def v1Description = "v1Description" @@ -86,6 +87,7 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { .withVersion(v1) .withVisible(v1Visible) .withEncryption(v1Encryption) + .withPreferred(v1Preferred) .withOciImageUrl(v1OciImageUrl) .withName(v1Name) .withDescription(v1Description) @@ -106,6 +108,7 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { and: "application has v2 with completely different metadata" def v2Visible = true def v2Encryption = true + def v2Preferred = true def v2OciImageUrl = "myregistry.local:5000/testing/test-image-updated" def v2Name = "v2NewName" def v2Description = "v2NewDescription" @@ -140,6 +143,7 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { .withId(appId) .withVisible(v2Visible) .withEncryption(v2Encryption) + .withPreferred(v2Preferred) .withOciImageUrl(v2OciImageUrl) .withName(v2Name) .withDescription(v2Description) @@ -173,6 +177,7 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { field().header().version().from(theBody1) == v1 field().header().visible().from(theBody1) == v1Visible field().header().encryption().from(theBody1) == v1Encryption + field().header().preferred().from(theBody1) == v1Preferred field().header().ociImageUrl().from(theBody1) == v1OciImageUrl field().header().name().from(theBody1) == v1Name field().header().category().from(theBody1) == String.valueOf(v1Category) @@ -202,7 +207,6 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { assertThat(field().versions().from(theBody1)).asList().hasSize(1) field().versions().at(0).version().from(theBody1) == v1 field().versions().at(0).visible().from(theBody1) == v1Visible - field().versions().at(0).encryption().from(theBody1) == v1Encryption and: "the body exposes requirements section with dependencies information" assertThat(field().requirements().dependencies().id().from(theBody1)).asList().containsExactlyInAnyOrder(v1Dependency1Id, v1Dependency2Id) @@ -241,6 +245,7 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { field().header().version().from(theBody2) == v1 field().header().visible().from(theBody2) == null // STB should not see this field field().header().encryption().from(theBody2) == null // STB should not see this field + field().header().preferred().from(theBody2) == null // STB should not see this field field().header().ociImageUrl().from(theBody2) == null // STB should not see this field field().header().category().from(theBody2) == String.valueOf(v2Category) field().header().name().from(theBody2) == v2Name @@ -264,7 +269,6 @@ class DevApiFTSpecSanity extends AsmsSanitySpecBase { assertThat(field().versions().from(theBody2)).asList().hasSize(1) field().versions().at(0).version().from(theBody2) == v1 field().versions().at(0).visible().from(theBody2) == null // STB should not see this field - field().versions().at(0).encryption().from(theBody2) == null // STB should not see this field and: "the body exposes requirements section with dependencies information" assertThat(field().requirements().dependencies().id().from(theBody2)).asList().containsExactlyInAnyOrder(v2Dependency1Id, v2Dependency2Id) diff --git a/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/request/ApplicationMetadataBuilder.java b/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/request/ApplicationMetadataBuilder.java index 98902e6..5b62c81 100644 --- a/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/request/ApplicationMetadataBuilder.java +++ b/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/request/ApplicationMetadataBuilder.java @@ -49,6 +49,7 @@ public class ApplicationMetadataBuilder { private String headerIcon; private Boolean headerVisible; private Boolean headerEncryption; + private Boolean headerPreferred; private String headerOciImageUrl; private Category headerCategory; private List headerLocalizations; @@ -115,6 +116,11 @@ public ApplicationMetadataBuilder withEncryption(Boolean headerEncryption) { return this; } + public ApplicationMetadataBuilder withPreferred(Boolean headerPreferred) { + this.headerPreferred = headerPreferred; + return this; + } + public ApplicationMetadataBuilder withOciImageUrl(String headerOciImageUrl) { this.headerOciImageUrl = headerOciImageUrl; return this; @@ -165,6 +171,9 @@ private ApplicationMetadataBuilder setFieldValue(String field, Object value) { case ApplicationDetailsPath.FIELD_ENCRYPTION: headerEncryption = Boolean.valueOf(String.valueOf(value)); break; + case ApplicationDetailsPath.FIELD_PREFERRED: + headerPreferred = Boolean.valueOf(String.valueOf(value)); + break; case ApplicationDetailsPath.FIELD_OCI_IMAGE_URL: headerOciImageUrl = String.valueOf(value); break; @@ -202,6 +211,7 @@ public ApplicationMetadataBuilder fromDefaults() { this.headerIcon = DataUtils.randomAppHeaderIcon(); this.headerVisible = Boolean.TRUE; this.headerEncryption = Boolean.FALSE; + this.headerPreferred = Boolean.FALSE; this.headerOciImageUrl = DataUtils.randomOciImageUrl(); this.headerCategory = Category.APPLICATION; this.platform = new Platform().architecture(DataUtils.randomPlatformArch()).os(DataUtils.randomPlatformOs()); @@ -216,6 +226,7 @@ public ApplicationMetadataBuilder fromExisting(Application existingApplication) headerVersion = existingHeader.getVersion(); headerVisible = existingHeader.isVisible(); headerEncryption = existingHeader.isEncryption(); + headerPreferred = existingHeader.isPreferred(); headerOciImageUrl = existingHeader.getOciImageUrl(); headerType = existingHeader.getType(); headerCategory = existingHeader.getCategory(); @@ -245,6 +256,7 @@ public Application forCreate() { .icon(headerIcon) .visible(headerVisible) .encryption(headerEncryption) + .preferred(headerPreferred) .ociImageUrl(headerOciImageUrl) .localization(headerLocalizations); @@ -263,6 +275,7 @@ public ApplicationForUpdate forUpdate() { .icon(headerIcon) .visible(headerVisible) .encryption(headerEncryption) + .preferred(headerPreferred) .ociImageUrl(headerOciImageUrl) .localization(headerLocalizations) .version(headerVersion); diff --git a/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/response/ApplicationDetailsPath.java b/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/response/ApplicationDetailsPath.java index bb4ca2f..c159605 100644 --- a/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/response/ApplicationDetailsPath.java +++ b/appstore-metadata-service-tests/src/test/java/com/lgi/appstore/metadata/test/framework/model/response/ApplicationDetailsPath.java @@ -27,6 +27,7 @@ public class ApplicationDetailsPath extends PathBase { public static final String FIELD_VERSION = "version"; public static final String FIELD_OCI_IMAGE_URL = "ociImageUrl"; public static final String FIELD_ENCRYPTION = "encryption"; + public static final String FIELD_PREFERRED = "preferred"; public static final String FIELD_VISIBLE = "visible"; public static final String FIELD_NAME = "name"; public static final String FIELD_CODE = "code"; @@ -85,6 +86,11 @@ public ApplicationDetailsPath encryption() { return this; } + public ApplicationDetailsPath preferred() { + fields.add(FIELD_PREFERRED); + return this; + } + public ApplicationDetailsPath ociImageUrl() { fields.add(FIELD_OCI_IMAGE_URL); return this; @@ -248,6 +254,8 @@ public static ApplicationDetailsPath extract(String field) { return field().header().visible(); case FIELD_ENCRYPTION: return field().header().encryption(); + case FIELD_PREFERRED: + return field().header().preferred(); case FIELD_OCI_IMAGE_URL: return field().header().ociImageUrl(); case FIELD_VERSION: diff --git a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsService.java b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsService.java index 3f6fa2b..9013956 100644 --- a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsService.java +++ b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsService.java @@ -33,6 +33,7 @@ import com.lgi.appstore.metadata.model.Meta; import com.lgi.appstore.metadata.model.Platform; import com.lgi.appstore.metadata.model.ResultSetMeta; +import com.lgi.appstore.metadata.util.ApplicationPreferredHelper; import com.lgi.appstore.metadata.util.ApplicationUrlService; import com.lgi.appstore.metadata.util.JsonProcessorHelper; import org.jooq.Condition; @@ -40,7 +41,7 @@ import org.jooq.JSONB; import org.jooq.Record; import org.jooq.Record1; -import org.jooq.Record12; +import org.jooq.Record13; import org.jooq.Record2; import org.jooq.SelectJoinStep; import org.jooq.SortField; @@ -95,7 +96,7 @@ public MaintainerApplicationsList listApplications(String maintainerCode, String .map(integerRecord1 -> integerRecord1.get(MAINTAINER.ID)) .orElseThrow(() -> new MaintainerNotFoundException(maintainerCode)); - final SelectJoinStep> from = dslContext + final SelectJoinStep> from = dslContext .select( APPLICATION.ID_RDOMAIN, APPLICATION.VERSION, @@ -104,6 +105,7 @@ public MaintainerApplicationsList listApplications(String maintainerCode, String APPLICATION.DESCRIPTION, APPLICATION.VISIBLE, APPLICATION.ENCRYPTION, + APPLICATION.PREFERRED, APPLICATION.OCI_IMAGE_URL, APPLICATION.TYPE, APPLICATION.SIZE, @@ -126,7 +128,8 @@ public MaintainerApplicationsList listApplications(String maintainerCode, String if (version != null) { condition = condition.and(APPLICATION.VERSION.eq(version)); } else { - condition = condition.and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'maintainer' = 'true'")); + condition = condition.and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'maintainer' = 'true'") + .or(APPLICATION.PREFERRED.eq(true))); } if (type != null) { condition = condition.and(APPLICATION.TYPE.contains(type)); @@ -151,11 +154,13 @@ public MaintainerApplicationsList listApplications(String maintainerCode, String final int effectiveOffset = offset != null ? offset : 0; final int effectiveLimit = limit != null ? limit : 10; - final List applicationHeaderList = from + var result = from .where(condition) .offset(effectiveOffset) .limit(effectiveLimit) - .fetch() + .fetch(); + + final List applicationHeaderList = ApplicationPreferredHelper.matchByPreferredVersionForListMaintainer(result) .stream() .map(applicationMetadataRecord -> MaintainerApplicationHeaderMapper.map(applicationMetadataRecord, jsonProcessorHelper)) .collect(Collectors.toList()); @@ -212,6 +217,7 @@ public Optional getApplicationDetails(String maint APPLICATION.DESCRIPTION, APPLICATION.VISIBLE, APPLICATION.ENCRYPTION, + APPLICATION.PREFERRED, APPLICATION.OCI_IMAGE_URL, APPLICATION.VERSION, APPLICATION.TYPE, @@ -262,7 +268,7 @@ public Optional getApplicationDetails(String maint .visible(applicationVersionRecord.get(APPLICATION.VISIBLE)) ).collect(Collectors.toList()); - return dslContext.select( + var result = dslContext.select( MAINTAINER.NAME, MAINTAINER.ADDRESS, MAINTAINER.HOMEPAGE, @@ -271,6 +277,7 @@ public Optional getApplicationDetails(String maint APPLICATION.VERSION, APPLICATION.VISIBLE, APPLICATION.ENCRYPTION, + APPLICATION.PREFERRED, APPLICATION.OCI_IMAGE_URL, APPLICATION.ICON, APPLICATION.NAME, @@ -290,8 +297,11 @@ public Optional getApplicationDetails(String maint .on(MAINTAINER.ID.eq(APPLICATION.MAINTAINER_ID).and(MAINTAINER.CODE.eq(maintainerCode))) .where(APPLICATION.ID_RDOMAIN.eq(appId)) .and(APPLICATION.MAINTAINER_ID.eq(maintainerId)) - .and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " ->> 'maintainer' = 'true'")) - .fetchOptional() + .and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " ->> 'maintainer' = 'true'") + .or(APPLICATION.PREFERRED.eq(true))) + .fetch(); + + return ApplicationPreferredHelper.matchByPreferredVersionForDetailsMaintainer(result) .map(applicationMetadataRecord -> { final String url = createApplicationUrlFromApplicationRecord(applicationMetadataRecord, platformName, firmwareVer); return MaintainerApplicationDetailsMapper.map(applicationMetadataRecord, versions, jsonProcessorHelper, url); @@ -319,6 +329,7 @@ public void addApplication(String maintainerCode, Application application) { APPLICATION.VERSION, APPLICATION.VISIBLE, APPLICATION.ENCRYPTION, + APPLICATION.PREFERRED, APPLICATION.OCI_IMAGE_URL, APPLICATION.NAME, APPLICATION.DESCRIPTION, @@ -337,6 +348,7 @@ public void addApplication(String maintainerCode, Application application) { application.getHeader().getVersion(), application.getHeader().isVisible(), application.getHeader().isEncryption(), + application.getHeader().isPreferred(), application.getHeader().getOciImageUrl(), application.getHeader().getName(), application.getHeader().getDescription(), @@ -375,6 +387,7 @@ public boolean updateLatestApplication(String maintainerCode, String appId, Appl final int affectedRows = localDslContext.update(APPLICATION) .set(APPLICATION.VISIBLE, applicationForUpdate.getHeader().isVisible()) .set(APPLICATION.ENCRYPTION, applicationForUpdate.getHeader().isEncryption()) + .set(APPLICATION.PREFERRED, applicationForUpdate.getHeader().isPreferred()) .set(APPLICATION.OCI_IMAGE_URL, applicationForUpdate.getHeader().getOciImageUrl()) .set(APPLICATION.NAME, applicationForUpdate.getHeader().getName()) .set(APPLICATION.DESCRIPTION, applicationForUpdate.getHeader().getDescription()) @@ -394,7 +407,9 @@ public boolean updateLatestApplication(String maintainerCode, String appId, Appl .execute(); updateApplicationsLatestField(localDslContext, maintainerId, appId); - + if (applicationForUpdate.getHeader().isPreferred()) { + updateApplicationsPreferredFieldForLatest(localDslContext, maintainerId, appId); + } return affectedRows > 0; } ); @@ -415,6 +430,7 @@ public boolean updateApplication(String maintainerCode, String appId, String ver final int affectedRows = localDslContext.update(APPLICATION) .set(APPLICATION.VISIBLE, applicationForUpdate.getHeader().isVisible()) .set(APPLICATION.ENCRYPTION, applicationForUpdate.getHeader().isEncryption()) + .set(APPLICATION.PREFERRED, applicationForUpdate.getHeader().isPreferred()) .set(APPLICATION.OCI_IMAGE_URL, applicationForUpdate.getHeader().getOciImageUrl()) .set(APPLICATION.NAME, applicationForUpdate.getHeader().getName()) .set(APPLICATION.DESCRIPTION, applicationForUpdate.getHeader().getDescription()) @@ -434,7 +450,9 @@ public boolean updateApplication(String maintainerCode, String appId, String ver .execute(); updateApplicationsLatestField(localDslContext, maintainerId, appId); - + if (applicationForUpdate.getHeader().isPreferred()) { + updateApplicationsPreferredFieldForVersion(localDslContext, maintainerId, appId, version); + } return affectedRows > 0; } ); @@ -562,6 +580,36 @@ private void updateApplicationsLatestField(DSLContext localDslContext, Integer m } } + private void updateApplicationsPreferredFieldForVersion(DSLContext localDslContext, Integer maintainerId, String appId, String version) { + updateApplicationsAllPreferredFieldsToFalse(localDslContext, maintainerId, appId); + + localDslContext.update(APPLICATION) + .set(APPLICATION.PREFERRED, true) + .where(APPLICATION.MAINTAINER_ID.eq(maintainerId)) + .and(APPLICATION.ID_RDOMAIN.eq(appId)) + .and(APPLICATION.VERSION.eq(version)) + .execute(); + } + + private void updateApplicationsPreferredFieldForLatest(DSLContext localDslContext, Integer maintainerId, String appId) { + updateApplicationsAllPreferredFieldsToFalse(localDslContext, maintainerId, appId); + + localDslContext.update(APPLICATION) + .set(APPLICATION.PREFERRED, true) + .where(APPLICATION.MAINTAINER_ID.eq(maintainerId)) + .and(APPLICATION.ID_RDOMAIN.eq(appId)) + .and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'maintainer' = 'true'")) + .execute(); + } + + private void updateApplicationsAllPreferredFieldsToFalse(DSLContext localDslContext, Integer maintainerId, String appId) { + localDslContext.update(APPLICATION) + .set(APPLICATION.PREFERRED, false) + .where(APPLICATION.MAINTAINER_ID.eq(maintainerId)) + .and(APPLICATION.ID_RDOMAIN.eq(appId)) + .execute(); + } + private String createApplicationUrlFromApplicationRecord(Record applicationMetadataRecord, String platformName, String firmwareVer) { return applicationUrlService.createApplicationUrlFromApplicationRecord(new ApplicationUrlService.ApplicationUrlParams( applicationMetadataRecord.get(APPLICATION.TYPE), diff --git a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerApplicationHeaderMapper.java b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerApplicationHeaderMapper.java index 304a45a..b0a76ca 100644 --- a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerApplicationHeaderMapper.java +++ b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerApplicationHeaderMapper.java @@ -54,6 +54,7 @@ public static MaintainerApplicationHeader map(Record applicationMetadataRecord, .description(applicationMetadataRecord.get(APPLICATION.DESCRIPTION)) .visible(applicationMetadataRecord.get(APPLICATION.VISIBLE)) .encryption(applicationMetadataRecord.get(APPLICATION.ENCRYPTION)) + .preferred(applicationMetadataRecord.get(APPLICATION.PREFERRED)) .ociImageUrl(applicationMetadataRecord.get(APPLICATION.OCI_IMAGE_URL)) .type(applicationMetadataRecord.get(APPLICATION.TYPE)) .size(applicationMetadataRecord.get(APPLICATION.SIZE)) diff --git a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerSingleApplicationHeaderMapper.java b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerSingleApplicationHeaderMapper.java index 0b3b3f9..572451e 100644 --- a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerSingleApplicationHeaderMapper.java +++ b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/mapper/MaintainerSingleApplicationHeaderMapper.java @@ -54,6 +54,7 @@ public static MaintainerSingleApplicationHeader map(Record applicationMetadataRe .description(applicationMetadataRecord.get(APPLICATION.DESCRIPTION)) .visible(applicationMetadataRecord.get(APPLICATION.VISIBLE)) .encryption(applicationMetadataRecord.get(APPLICATION.ENCRYPTION)) + .preferred(applicationMetadataRecord.get(APPLICATION.PREFERRED)) .ociImageUrl(applicationMetadataRecord.get(APPLICATION.OCI_IMAGE_URL)) .url(url) .type(applicationMetadataRecord.get(APPLICATION.TYPE)) diff --git a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/stb/PersistentAppsService.java b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/stb/PersistentAppsService.java index 8ee6b18..f0c13d3 100644 --- a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/stb/PersistentAppsService.java +++ b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/api/stb/PersistentAppsService.java @@ -32,6 +32,7 @@ import com.lgi.appstore.metadata.model.StbApplicationsList; import com.lgi.appstore.metadata.model.StbSingleApplicationHeader; import com.lgi.appstore.metadata.model.StbVersion; +import com.lgi.appstore.metadata.util.ApplicationPreferredHelper; import com.lgi.appstore.metadata.util.ApplicationUrlService; import com.lgi.appstore.metadata.util.JsonProcessorHelper; import org.jooq.Condition; @@ -39,7 +40,7 @@ import org.jooq.JSONB; import org.jooq.Record; import org.jooq.Record1; -import org.jooq.Record9; +import org.jooq.Record10; import org.jooq.SelectConditionStep; import org.jooq.SortField; import org.jooq.impl.DSL; @@ -116,7 +117,7 @@ public StbApplicationsList listApplications(String name, String maintainerName, Integer offset, Integer limit) { - final SelectConditionStep> where = dslContext.select( + final SelectConditionStep> where = dslContext.select( APPLICATION.ID_RDOMAIN, APPLICATION.VERSION, APPLICATION.ICON, @@ -125,7 +126,8 @@ public StbApplicationsList listApplications(String name, APPLICATION.TYPE, APPLICATION.SIZE, APPLICATION.CATEGORY, - APPLICATION.LOCALIZATIONS) + APPLICATION.LOCALIZATIONS, + APPLICATION.PREFERRED) .from(APPLICATION) .leftJoin(MAINTAINER) .on(APPLICATION.MAINTAINER_ID.eq(MAINTAINER.ID)) @@ -147,7 +149,8 @@ public StbApplicationsList listApplications(String name, if (version != null) { condition = condition.and(APPLICATION.VERSION.eq(version)); } else { - condition = condition.and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'stb' = 'true'")); + condition = condition.and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'stb' = 'true'") + .or(APPLICATION.PREFERRED.eq(true))); } if (type != null) { condition = condition.and(APPLICATION.TYPE.contains(type)); @@ -177,13 +180,15 @@ public StbApplicationsList listApplications(String name, final int effectiveOffset = offset != null ? offset : 0; final int effectiveLimit = limit != null ? limit : 10; - final List applicationHeaderList = where + var result = where .and(condition) .offset(effectiveOffset) .limit(effectiveLimit) - .fetch() - .stream() - .map(applicationMetadataRecord -> new StbApplicationHeader() + .fetch(); + + final List applicationHeaderList = ApplicationPreferredHelper.matchByPreferredVersionForListStb(result) + .stream() + .map(applicationMetadataRecord -> new StbApplicationHeader() .id(applicationMetadataRecord.get(APPLICATION.ID_RDOMAIN)) .version(applicationMetadataRecord.get(APPLICATION.VERSION)) .icon(applicationMetadataRecord.get(APPLICATION.ICON)) @@ -228,7 +233,7 @@ public Optional getApplicationDetails(String appId, Strin .map(applicationVersionRecord -> new StbVersion() .version(applicationVersionRecord.get(APPLICATION.VERSION))).collect(Collectors.toList()); - return dslContext.select( + var result = dslContext.select( MAINTAINER.CODE, MAINTAINER.NAME, MAINTAINER.ADDRESS, @@ -247,7 +252,8 @@ public Optional getApplicationDetails(String appId, Strin APPLICATION.FEATURES, APPLICATION.DEPENDENCIES, APPLICATION.SIZE, - APPLICATION.OCI_IMAGE_URL + APPLICATION.OCI_IMAGE_URL, + APPLICATION.PREFERRED ) .from(MAINTAINER) @@ -256,7 +262,9 @@ public Optional getApplicationDetails(String appId, Strin .where(APPLICATION.ID_RDOMAIN.eq(appId)) .and(APPLICATION.VERSION.eq(version)) .and(APPLICATION.VISIBLE.eq(true)) - .fetchOptional() + .fetch(); + + return ApplicationPreferredHelper.matchByPreferredVersionForDetailsStb(result) .map(applicationMetadataRecord -> { final StbSingleApplicationHeader applicationHeader = new StbSingleApplicationHeader() .id(applicationMetadataRecord.get(APPLICATION.ID_RDOMAIN)) @@ -313,7 +321,7 @@ public Optional getApplicationDetails(String appId, Strin .map(applicationVersionRecord -> new StbVersion() .version(applicationVersionRecord.get(APPLICATION.VERSION))).collect(Collectors.toList()); - return dslContext.select( + var result = dslContext.select( MAINTAINER.CODE, MAINTAINER.NAME, MAINTAINER.ADDRESS, @@ -332,16 +340,20 @@ public Optional getApplicationDetails(String appId, Strin APPLICATION.FEATURES, APPLICATION.DEPENDENCIES, APPLICATION.SIZE, - APPLICATION.OCI_IMAGE_URL + APPLICATION.OCI_IMAGE_URL, + APPLICATION.PREFERRED ) .from(MAINTAINER) .innerJoin(APPLICATION) .on(MAINTAINER.ID.eq(APPLICATION.MAINTAINER_ID)) .where(APPLICATION.ID_RDOMAIN.eq(appId)) - .and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'stb' = 'true'")) + .and(DSL.condition(APPLICATION.LATEST.getQualifiedName() + " -> 'stb' = 'true'") + .or(APPLICATION.PREFERRED.eq(true))) .and(APPLICATION.VISIBLE.eq(true)) - .fetchOptional() + .fetch(); + + return ApplicationPreferredHelper.matchByPreferredVersionForDetailsStb(result) .map(applicationMetadataRecord -> { final StbSingleApplicationHeader applicationHeader = new StbSingleApplicationHeader() .id(applicationMetadataRecord.get(APPLICATION.ID_RDOMAIN)) diff --git a/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/util/ApplicationPreferredHelper.java b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/util/ApplicationPreferredHelper.java new file mode 100644 index 0000000..29cce50 --- /dev/null +++ b/appstore-metadata-service/src/main/java/com/lgi/appstore/metadata/util/ApplicationPreferredHelper.java @@ -0,0 +1,78 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Liberty Global Technology Services BV + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.lgi.appstore.metadata.util; + +import org.jooq.JSONB; +import org.jooq.Record10; +import org.jooq.Record13; +import org.jooq.Record20; +import org.jooq.Record21; +import org.jooq.Result; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.lgi.appstore.metadata.jooq.model.tables.Application.APPLICATION; + +public class ApplicationPreferredHelper { + + private ApplicationPreferredHelper() { + } + + public static List> + matchByPreferredVersionForListStb(Result> result) { + return result.stream() + .filter(record1 -> result.stream() + .filter(record2 -> record1 != record2) + .noneMatch(record2 -> record1.get(APPLICATION.ID_RDOMAIN).equals(record2.get(APPLICATION.ID_RDOMAIN)) + && record2.get(APPLICATION.PREFERRED).equals(true)) + ).collect(Collectors.toList()); + } + + public static List> + matchByPreferredVersionForListMaintainer(Result> result) { + return result.stream() + .filter(record1 -> result.stream() + .filter(record2 -> record1 != record2) + .noneMatch(record2 -> record1.get(APPLICATION.ID_RDOMAIN).equals(record2.get(APPLICATION.ID_RDOMAIN)) + && record2.get(APPLICATION.PREFERRED).equals(true)) + ).collect(Collectors.toList()); + } + + public static Optional> + matchByPreferredVersionForDetailsStb(Result> result) { + if (result.size() > 1) { + return result.stream() + .filter(record -> record.get(APPLICATION.PREFERRED).equals(true)) + .findAny(); + } + return result.stream().findAny(); + } + + public static Optional> + matchByPreferredVersionForDetailsMaintainer(Result> result) { + if (result.size() > 1) { + return result.stream() + .filter(record -> record.get(APPLICATION.PREFERRED).equals(true)) + .findAny(); + } + return result.stream().findAny(); + } +} diff --git a/appstore-metadata-service/src/main/resources/db/migration/V05__Add_preferred_column.sql b/appstore-metadata-service/src/main/resources/db/migration/V05__Add_preferred_column.sql new file mode 100644 index 0000000..2e4c460 --- /dev/null +++ b/appstore-metadata-service/src/main/resources/db/migration/V05__Add_preferred_column.sql @@ -0,0 +1,20 @@ +-- +-- If not stated otherwise in this file or this component's LICENSE file the +-- following copyright and licenses apply: +-- +-- Copyright 2023 Liberty Global Technology Services BV +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +alter table application add column preferred boolean default false not null; \ No newline at end of file diff --git a/appstore-metadata-service/src/main/resources/static/appstore-metadata-service.yaml b/appstore-metadata-service/src/main/resources/static/appstore-metadata-service.yaml index 8a104a5..44d01ff 100644 --- a/appstore-metadata-service/src/main/resources/static/appstore-metadata-service.yaml +++ b/appstore-metadata-service/src/main/resources/static/appstore-metadata-service.yaml @@ -22,7 +22,7 @@ openapi: 3.0.0 info: title: ASMS API description: AppStore Metadata Service REST API. MAS API in RDK. - version: 0.6.0 + version: 0.7.0 license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html @@ -738,6 +738,11 @@ components: description: Bundle encryption attribute default: false example: false + preferred: + type: boolean + description: Preferred attribute + default: false + example: false ociImageUrl: type: string description: OCI Image URL @@ -760,6 +765,11 @@ components: description: Bundle encryption attribute default: false example: false + preferred: + type: boolean + description: Preferred attribute + default: false + example: false ociImageUrl: type: string description: OCI Image URL diff --git a/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/MaintainerAppsControllerTest.java b/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/MaintainerAppsControllerTest.java index 9d4adfe..43cd82b 100644 --- a/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/MaintainerAppsControllerTest.java +++ b/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/MaintainerAppsControllerTest.java @@ -111,7 +111,8 @@ public void setup() { "Container contains both Flutter application and Flutter engine running on wayland-egl, developed by Liberty Global while evaluating Google Flutter UI toolkit.") .category(Category.APPLICATION) .visible(true) - .encryption(false); + .encryption(false) + .preferred(false); private static final MaintainerApplicationHeader WAYLAND_EGL_TEST_APPLICATION_HEADER = new MaintainerApplicationHeader() .id("com.libertyglobal.app.waylandegltest") @@ -124,7 +125,8 @@ public void setup() { "Source code example of simple Wayland EGL application intended as tutorial for developers. Contains the few but necessary setup code for any direct to wayland-egl client application such as how to connect to wayland server, create/use EGL surface and draw on screen via opengles api. Application shows simple rectangle on screen. Applications based on this example should run on the various wayland compositors supporting the wayland-egl protocol out there.") .category(Category.APPLICATION) .visible(true) - .encryption(false); + .encryption(false) + .preferred(false); private static final MaintainerApplicationHeader YOU_I_APPLICATION_HEADER = new MaintainerApplicationHeader() .id("com.libertyglobal.app.youi") @@ -138,6 +140,7 @@ public void setup() { .category(Category.APPLICATION) .visible(true) .encryption(false) + .preferred(false) .localization( List.of( new Localization() @@ -167,6 +170,7 @@ public void setup() { .category(Category.APPLICATION) .visible(true) .encryption(false) + .preferred(false) .localization( List.of( new Localization() @@ -214,6 +218,7 @@ public void setup() { " \"version\": \"1.2.3\"," + " \"visible\": true," + " \"encryption\": false," + + " \"preferred\": false," + " \"ociImageUrl\": \"${OCI_IMAGE_URL}\"," + " \"latest\": true," + " \"name\": \"Awesome Application\"," + @@ -265,6 +270,7 @@ public void setup() { " \"icon\": \"https://libertyglobal.com/s/apps/com.libertyglobal.app.awesome/1.2.3/image/1920x1080/icon.png\"," + " \"visible\": true," + " \"encryption\": false," + + " \"preferred\": false," + " \"ociImageUrl\": \"ociImageUrl\"," + " \"latest\": true," + " \"name\": \"Awesome Application\"," + diff --git a/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsServiceTest.java b/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsServiceTest.java index ce3990c..7370ea9 100644 --- a/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsServiceTest.java +++ b/appstore-metadata-service/src/test/java/com/lgi/appstore/metadata/api/maintainer/PersistentAppsServiceTest.java @@ -385,6 +385,7 @@ private MaintainerApplicationHeader createRandomMaintainerApplicationHeader(Loca .localization(Collections.singletonList(localization)) .visible(true) .encryption(false) + .preferred(false) .ociImageUrl("OCI_IMAGE_URL"); } @@ -403,6 +404,7 @@ private ApplicationHeaderForUpdate createRandomApplicationHeaderForUpdate(Locali .localization(Collections.singletonList(localization)) .visible(true) .encryption(false) + .preferred(false) .ociImageUrl("UPDATED_OCI_IMAGE_URL") .version(version); } @@ -423,6 +425,7 @@ private void verifyMaintainerApplicationDetails(Optional