From 9fb66a5ad0a5d99c4d9ea18a4f61f4ed6ec3771a Mon Sep 17 00:00:00 2001 From: lindseydew Date: Tue, 15 Sep 2026 15:03:15 +0100 Subject: [PATCH 1/8] Add capi usages endpoint to media api --- build.sbt | 2 + media-api/app/MediaApiComponents.scala | 4 +- media-api/app/controllers/MediaApi.scala | 10 +++ media-api/app/lib/ContentApi.scala | 86 +++++++++++++++++++ media-api/app/lib/MediaApiConfig.scala | 6 +- media-api/app/models/ImageUsages.scala | 24 ++++++ media-api/conf/routes | 1 + .../test/lib/elasticsearch/Fixtures.scala | 6 +- 8 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 media-api/app/lib/ContentApi.scala create mode 100644 media-api/app/models/ImageUsages.scala diff --git a/build.sbt b/build.sbt index dad2f1577af..4704c5f588c 100644 --- a/build.sbt +++ b/build.sbt @@ -161,6 +161,8 @@ lazy val mediaApi = playProject("media-api", 9001) .settings( libraryDependencies ++= Seq( "org.apache.commons" % "commons-email" % "1.5", + "com.gu" %% "content-api-client-default" % "32.0.0", + "com.gu" %% "content-api-client-aws" % "1.0.1", "org.parboiled" %% "parboiled" % "2.1.7", "org.http4s" %% "http4s-core" % "0.23.17", "com.github.blemale" %% "scaffeine" % "5.3.0" diff --git a/media-api/app/MediaApiComponents.scala b/media-api/app/MediaApiComponents.scala index dd7a1e99618..aae87ce8ace 100644 --- a/media-api/app/MediaApiComponents.scala +++ b/media-api/app/MediaApiComponents.scala @@ -1,3 +1,4 @@ +import com.gu.contentapi.client.ScheduledExecutor import com.gu.mediaservice.lib.aws.{Bedrock, Embedder, S3, S3Vectors, SimpleSqsMessageConsumer, ThrallMessageSender} import com.gu.mediaservice.lib.management.{ElasticSearchHealthCheck, InnerServiceStatusCheckController, Management} import com.gu.mediaservice.lib.metadata.SoftDeletedMetadataTable @@ -30,8 +31,9 @@ class MediaApiComponents(context: Context) extends GridComponents(context, new M val softDeletedMetadataTable = new SoftDeletedMetadataTable(config) val embedder = new Embedder(new Bedrock(config), new SimpleSqsMessageConsumer(config.queueUrl, config)) + val previewContentApi = new PreviewContentApi(config)(ScheduledExecutor()) - val mediaApi = new MediaApi(auth, messageSender, softDeletedMetadataTable, elasticSearch, imageResponse, config, controllerComponents, s3Client, mediaApiMetrics, wsClient, authorisation, embedder) + val mediaApi = new MediaApi(auth, messageSender, softDeletedMetadataTable, elasticSearch, imageResponse, config, liveContentApi, previewContentApi, controllerComponents, s3Client, mediaApiMetrics, wsClient, authorisation, embedder) val suggestionController = new SuggestionController(auth, elasticSearch, controllerComponents) val aggController = new AggregationController(auth, elasticSearch, controllerComponents) val usageController = new UsageController(auth, config, elasticSearch, usageQuota, controllerComponents) diff --git a/media-api/app/controllers/MediaApi.scala b/media-api/app/controllers/MediaApi.scala index 1f068099831..5e4b2301fb7 100644 --- a/media-api/app/controllers/MediaApi.scala +++ b/media-api/app/controllers/MediaApi.scala @@ -20,6 +20,7 @@ import com.sksamuel.elastic4s.requests.searches.queries.Query import lib._ import lib.elasticsearch._ import lib.querysyntax.Condition +import models.ImageUsages import org.apache.http.entity.ContentType import org.apache.pekko.stream.scaladsl.StreamConverters import org.http4s.UriTemplate @@ -50,6 +51,7 @@ class MediaApi( elasticSearch: ElasticSearch, imageResponse: ImageResponse, config: MediaApiConfig, + previewContentApi: PreviewContentApi, override val controllerComponents: ControllerComponents, s3Client: S3, mediaApiMetrics: MediaApiMetrics, @@ -182,6 +184,14 @@ class MediaApi( case _ => ImageNotFound(id) } } + def getCapiUsages(id: String) = auth.async { _ => + for { + previewContent <- previewContentApi.findContentUsingImage(id) + previewImages = previewContent.map(sr => ImageUsages.fromSearchResponse(sr)) + } yield { + respond[List[ImageUsages]](previewImages) + } + } /** * Get the raw response from ElasticSearch. diff --git a/media-api/app/lib/ContentApi.scala b/media-api/app/lib/ContentApi.scala new file mode 100644 index 00000000000..9cd121bf01d --- /dev/null +++ b/media-api/app/lib/ContentApi.scala @@ -0,0 +1,86 @@ +package lib + +import com.gu.contentapi.client.model.v1.Content +import com.gu.contentapi.client.model.{HttpResponse, ItemQuery, SearchQuery} +import com.gu.contentapi.client._ +import com.gu.mediaservice.lib.config.CommonConfig +import software.amazon.awssdk.auth.credentials.{AwsCredentialsProvider, ProfileCredentialsProvider} +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.sts.StsClient +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider +import software.amazon.awssdk.services.sts.model.AssumeRoleRequest + +import java.net.URI +import scala.concurrent.duration.DurationInt +import scala.concurrent.{ExecutionContext, Future} + +abstract class ContentApiClient(config: MediaApiConfig)(implicit val executor: ScheduledExecutor) + extends GuardianContentClient(apiKey = config.capiApiKey) { + + def imageSearchQuery(imageId: String): SearchQuery = { + SearchQuery() + .q(imageId) + .queryFields("body,main,thumbnail") + .showFields("firstPublicationDate,isLive,internalComposerCode") + } + + def findContentUsingImage(imageId: String)(implicit context: ExecutionContext): Future[List[Content]] = { + val imageSearchQ = imageSearchQuery(imageId) + paginateAccum(imageSearchQ)(sr => sr.results.toList, (l1: List[Content], l2: List[Content]) => l1 ++ l2) + } + +} + +class PreviewContentApi(protected val config: MediaApiConfig)(implicit val ex: ScheduledExecutor) + // ensure IAMAuthContentApiClient is the first trait in this list! + extends ContentApiClient(config) with IAMAuthContentApiClient with RetryableContentApiClient { + + override val targetUrl: String = config.capiPreviewUrl + override val backoffStrategy: BackoffStrategy = BackoffStrategy.doublingStrategy(2.seconds, config.capiMaxRetries) +} + +// order of mixing is important. Some client traits (notably RetryableContentApiClient!) +// also override get, adding header(s) (and could potentially edit the uri too) before calling super.get(). Those +// traits must be executed BEFORE this trait, so that the get override in this trait +// receives the headers that will actually be sent over the wire. +// so any class mixing this in should have it first in the list of traits, eg. +// class MyCapiClient extends GuardianContentApiClient(apiKey) +// with IAMAuthContentApiClient with RetryableContentApiClient with MyOtherClientTraits +// ie. the super calls will travel "from right to left" along the trait list, and this trait can sign the accumulated headers +trait IAMAuthContentApiClient extends ContentApiClient { + protected val config: CommonConfig + + lazy val sts: StsClient = StsClient.builder() + .region(Region.of(config.awsRegion.id())) + .build() + + private lazy val sessionId: String = "session-" + Math.random() + lazy val capiCredentials: AwsCredentialsProvider = + config.capiPreviewRole.map(arn => { + + val assumeRoleRequest = AssumeRoleRequest.builder().roleArn(arn).roleSessionName(sessionId).build() + + StsAssumeRoleCredentialsProvider.builder() + .refreshRequest(assumeRoleRequest) + .stsClient(sts) + .build() + }).getOrElse(ProfileCredentialsProvider.create("capi")) // will be used if stream is ever run locally (unusual) + + abstract override def get( + url: String, + headers: Map[String, String] + )(implicit context: ExecutionContext): Future[HttpResponse] = { + + val uri = new URI(url) + val encodedQuery = IAMEncoder.encodeParams(uri.getQuery) + + // no mutation of uris, and no easy way to create from a given one + val encodedUri = new URI(uri.getScheme, uri.getAuthority, uri.getPath, encodedQuery, uri.getFragment) + + val signer = new IAMSigner(capiCredentials, config.awsRegion.id()) + + val withIamHeaders = signer.addIAMHeaders(headers, encodedUri) + + super.get(encodedUri.toString, withIamHeaders) + } +} diff --git a/media-api/app/lib/MediaApiConfig.scala b/media-api/app/lib/MediaApiConfig.scala index 3d98a0e4dc7..8cafc7f1e61 100644 --- a/media-api/app/lib/MediaApiConfig.scala +++ b/media-api/app/lib/MediaApiConfig.scala @@ -56,7 +56,11 @@ class MediaApiConfig(resources: GridConfigResources) extends CommonConfigWithEla val collectionsUri: String = services.collectionsBaseUri val requiredMetadata = NonEmptyList("credit", "description", "usageRights") - + val capiPreviewUrl = string("capi.preview.url") + val capiPreviewRole = stringOpt("capi.preview.role") + val defaultMaxRetries = 4 + val capiMaxRetries: Int = intDefault("capi.maxRetries", defaultMaxRetries) + val capiApiKey = string("capi.apiKey") val syndicationStartDate: Option[DateTime] = Try { stringOpt("syndication.start").map(d => DateTime.parse(d).withTimeAtStartOfDay()) }.toOption.flatten diff --git a/media-api/app/models/ImageUsages.scala b/media-api/app/models/ImageUsages.scala new file mode 100644 index 00000000000..88222f5dd4d --- /dev/null +++ b/media-api/app/models/ImageUsages.scala @@ -0,0 +1,24 @@ +package models + +import com.gu.contentapi.client.model.v1.Content + +case class ImageUsages(contentId: String, webTitle: String, webUrl: String, composerId: Option[String], publishedAt: Option[Long] = None, isLive: Option[Boolean] = None) + +object ImageUsages { + + import play.api.libs.json._ + + implicit val imageUsagesWrites: Writes[ImageUsages] = Json.writes[ImageUsages] + implicit val imageUsagesReads: Reads[ImageUsages] = Json.reads[ImageUsages] + + def fromSearchResponse(content: Content) = { + ImageUsages( + content.id, + content.webTitle, + content.webUrl, + content.fields.flatMap(_.internalComposerCode), + content.fields.flatMap(_.firstPublicationDate.map(_.dateTime)), + content.fields.flatMap(_.isLive) + ) + } +} diff --git a/media-api/conf/routes b/media-api/conf/routes index 30f5196932f..72f8ff428e9 100644 --- a/media-api/conf/routes +++ b/media-api/conf/routes @@ -14,6 +14,7 @@ GET /images/aggregations/date/:field controllers. # Images GET /images/:id controllers.MediaApi.getImage(id: String) +GET /capiUsages/:id controllers.MediaApi.getCapiUsages(id: String) GET /images/:id/_elasticsearch controllers.MediaApi.getImageFromElasticSearch(id: String) GET /images/:id/projection/diff controllers.MediaApi.diffProjection(id: String) GET /images/:id/fileMetadata controllers.MediaApi.getImageFileMetadata(id: String) diff --git a/media-api/test/lib/elasticsearch/Fixtures.scala b/media-api/test/lib/elasticsearch/Fixtures.scala index 57fe3c005de..621f0338809 100644 --- a/media-api/test/lib/elasticsearch/Fixtures.scala +++ b/media-api/test/lib/elasticsearch/Fixtures.scala @@ -39,7 +39,11 @@ trait Fixtures { "s3.image.bucket", "s3.thumb.bucket", "grid.stage", - "grid.appName" + "grid.appName", + "capi.live.url", + "capi.apiKey", + "capi.preview.role", + "capi.preview.url", ) def deletionData(deletedBy: String): SoftDeletedMetadata = SoftDeletedMetadata( From 3fb423c377ce5fe17943844c556d9e1e648a3747 Mon Sep 17 00:00:00 2001 From: lindseydew Date: Tue, 15 Sep 2026 16:27:04 +0100 Subject: [PATCH 2/8] Add config variables --- build.sbt | 1 + media-api/app/MediaApiComponents.scala | 2 +- media-api/app/controllers/MediaApi.scala | 6 ++++-- media-api/app/lib/ContentApi.scala | 4 ++-- media-api/app/lib/MediaApiConfig.scala | 3 +++ media-api/app/models/ImageUsages.scala | 5 +++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 4704c5f588c..82947ea1db5 100644 --- a/build.sbt +++ b/build.sbt @@ -163,6 +163,7 @@ lazy val mediaApi = playProject("media-api", 9001) "org.apache.commons" % "commons-email" % "1.5", "com.gu" %% "content-api-client-default" % "32.0.0", "com.gu" %% "content-api-client-aws" % "1.0.1", + "software.amazon.awssdk" % "sts" % awsSdkV2Version, "org.parboiled" %% "parboiled" % "2.1.7", "org.http4s" %% "http4s-core" % "0.23.17", "com.github.blemale" %% "scaffeine" % "5.3.0" diff --git a/media-api/app/MediaApiComponents.scala b/media-api/app/MediaApiComponents.scala index aae87ce8ace..904972030c1 100644 --- a/media-api/app/MediaApiComponents.scala +++ b/media-api/app/MediaApiComponents.scala @@ -33,7 +33,7 @@ class MediaApiComponents(context: Context) extends GridComponents(context, new M val embedder = new Embedder(new Bedrock(config), new SimpleSqsMessageConsumer(config.queueUrl, config)) val previewContentApi = new PreviewContentApi(config)(ScheduledExecutor()) - val mediaApi = new MediaApi(auth, messageSender, softDeletedMetadataTable, elasticSearch, imageResponse, config, liveContentApi, previewContentApi, controllerComponents, s3Client, mediaApiMetrics, wsClient, authorisation, embedder) + val mediaApi = new MediaApi(auth, messageSender, softDeletedMetadataTable, elasticSearch, imageResponse, config, previewContentApi, controllerComponents, s3Client, mediaApiMetrics, wsClient, authorisation, embedder) val suggestionController = new SuggestionController(auth, elasticSearch, controllerComponents) val aggController = new AggregationController(auth, elasticSearch, controllerComponents) val usageController = new UsageController(auth, config, elasticSearch, usageQuota, controllerComponents) diff --git a/media-api/app/controllers/MediaApi.scala b/media-api/app/controllers/MediaApi.scala index 5e4b2301fb7..d374f77abb0 100644 --- a/media-api/app/controllers/MediaApi.scala +++ b/media-api/app/controllers/MediaApi.scala @@ -120,6 +120,7 @@ class MediaApi( val maybeLoaderLink: Option[Link] = Some(Link("loader", config.loaderUri)).filter(_ => userCanUpload) val maybeArchiveLink: Option[Link] = Some(Link("archive", s"${config.metadataUri}/metadata/{id}/archived")).filter(_ => userCanArchive) + val maybeCapiUsagesLink: Option[Link] = Some(Link("capiUsages", s"${config.rootUri}/capiUsages/{id}")).filter(_ => config.takedownEnabled) val indexLinks = List( searchLink, Link("image", s"${config.rootUri}/images/{id}"), @@ -137,7 +138,7 @@ class MediaApi( Link("syndicate-image", s"${config.rootUri}/images/{id}/{partnerName}/{startPending}/syndicateImage"), Link("undelete", s"${config.rootUri}/images/{id}/undelete"), Link("usage", config.usageUri), - ) ++ maybeLoaderLink.toList ++ maybeArchiveLink.toList + ) ++ maybeLoaderLink.toList ++ maybeArchiveLink.toList ++ maybeCapiUsagesLink.toList respond(indexData, indexLinks) } @@ -185,9 +186,10 @@ class MediaApi( } } def getCapiUsages(id: String) = auth.async { _ => + val composerDomain = config.composerDomain for { previewContent <- previewContentApi.findContentUsingImage(id) - previewImages = previewContent.map(sr => ImageUsages.fromSearchResponse(sr)) + previewImages = previewContent.map(sr => ImageUsages.fromSearchResponse(sr, composerDomain)) } yield { respond[List[ImageUsages]](previewImages) } diff --git a/media-api/app/lib/ContentApi.scala b/media-api/app/lib/ContentApi.scala index 9cd121bf01d..479b2a21059 100644 --- a/media-api/app/lib/ContentApi.scala +++ b/media-api/app/lib/ContentApi.scala @@ -48,10 +48,10 @@ class PreviewContentApi(protected val config: MediaApiConfig)(implicit val ex: S // with IAMAuthContentApiClient with RetryableContentApiClient with MyOtherClientTraits // ie. the super calls will travel "from right to left" along the trait list, and this trait can sign the accumulated headers trait IAMAuthContentApiClient extends ContentApiClient { - protected val config: CommonConfig + protected val config: MediaApiConfig lazy val sts: StsClient = StsClient.builder() - .region(Region.of(config.awsRegion.id())) + .region(Region.of(config.awsRegionName)) .build() private lazy val sessionId: String = "session-" + Math.random() diff --git a/media-api/app/lib/MediaApiConfig.scala b/media-api/app/lib/MediaApiConfig.scala index 8cafc7f1e61..39ad93deea1 100644 --- a/media-api/app/lib/MediaApiConfig.scala +++ b/media-api/app/lib/MediaApiConfig.scala @@ -26,6 +26,7 @@ class MediaApiConfig(resources: GridConfigResources) extends CommonConfigWithEla //Lazy allows this to be empty and not break things unless used somewhere lazy val imgPublishingBucket = string("publishing.image.bucket") + val awsRegionName = string("aws.region") val imageBucket: String = string("s3.image.bucket") val thumbBucket: String = string("s3.thumb.bucket") @@ -61,6 +62,7 @@ class MediaApiConfig(resources: GridConfigResources) extends CommonConfigWithEla val defaultMaxRetries = 4 val capiMaxRetries: Int = intDefault("capi.maxRetries", defaultMaxRetries) val capiApiKey = string("capi.apiKey") + val composerDomain = string("composer.domain") val syndicationStartDate: Option[DateTime] = Try { stringOpt("syndication.start").map(d => DateTime.parse(d).withTimeAtStartOfDay()) }.toOption.flatten @@ -77,6 +79,7 @@ class MediaApiConfig(resources: GridConfigResources) extends CommonConfigWithEla configuration.getOptional[Map[String, String]]("usageRestrictions").getOrElse(Map.empty) val restrictDownload: Boolean = boolean("restrictDownload") + val takedownEnabled: Boolean = booleanOpt("takedown.enabled").getOrElse(false) val queueUrl: String = stringOpt("sqs.embedder.queue.url").getOrElse("") diff --git a/media-api/app/models/ImageUsages.scala b/media-api/app/models/ImageUsages.scala index 88222f5dd4d..e90bbd9fd43 100644 --- a/media-api/app/models/ImageUsages.scala +++ b/media-api/app/models/ImageUsages.scala @@ -1,6 +1,7 @@ package models import com.gu.contentapi.client.model.v1.Content +import lib.MediaApiConfig case class ImageUsages(contentId: String, webTitle: String, webUrl: String, composerId: Option[String], publishedAt: Option[Long] = None, isLive: Option[Boolean] = None) @@ -11,12 +12,12 @@ object ImageUsages { implicit val imageUsagesWrites: Writes[ImageUsages] = Json.writes[ImageUsages] implicit val imageUsagesReads: Reads[ImageUsages] = Json.reads[ImageUsages] - def fromSearchResponse(content: Content) = { + def fromSearchResponse(content: Content, composerDomain: String) = { ImageUsages( content.id, content.webTitle, content.webUrl, - content.fields.flatMap(_.internalComposerCode), + content.fields.flatMap(_.internalComposerCode.map(code => s"$composerDomain$code")), content.fields.flatMap(_.firstPublicationDate.map(_.dateTime)), content.fields.flatMap(_.isLive) ) From 95e17d38d7aaa2307345b6eae05b61d50cead36d Mon Sep 17 00:00:00 2001 From: lindseydew Date: Tue, 15 Sep 2026 16:39:09 +0100 Subject: [PATCH 3/8] Update test fixtures --- media-api/test/lib/elasticsearch/Fixtures.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/media-api/test/lib/elasticsearch/Fixtures.scala b/media-api/test/lib/elasticsearch/Fixtures.scala index 621f0338809..bdc5ca3fb98 100644 --- a/media-api/test/lib/elasticsearch/Fixtures.scala +++ b/media-api/test/lib/elasticsearch/Fixtures.scala @@ -21,6 +21,7 @@ trait Fixtures { "usageRights" -> Map( "applicable" -> List() ), + "aws.region" -> "eu-west-1", "usageRightsConfigProvider" -> "com.gu.mediaservice.lib.config.RuntimeUsageRightsConfig" ) val NOT_USED_IN_TEST = "not used in test" @@ -44,6 +45,7 @@ trait Fixtures { "capi.apiKey", "capi.preview.role", "capi.preview.url", + "composer.domain" ) def deletionData(deletedBy: String): SoftDeletedMetadata = SoftDeletedMetadata( From 5a6354484b4f9ccd0e33d1989140b58cf2d8de20 Mon Sep 17 00:00:00 2001 From: Lindsey Dew Date: Tue, 15 Sep 2026 17:22:27 +0100 Subject: [PATCH 4/8] Update ImageUsages.scala --- media-api/app/models/ImageUsages.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/media-api/app/models/ImageUsages.scala b/media-api/app/models/ImageUsages.scala index e90bbd9fd43..dc2ccb79b84 100644 --- a/media-api/app/models/ImageUsages.scala +++ b/media-api/app/models/ImageUsages.scala @@ -3,7 +3,7 @@ package models import com.gu.contentapi.client.model.v1.Content import lib.MediaApiConfig -case class ImageUsages(contentId: String, webTitle: String, webUrl: String, composerId: Option[String], publishedAt: Option[Long] = None, isLive: Option[Boolean] = None) +case class ImageUsages(contentId: String, webTitle: String, webUrl: String, composerUrl: Option[String], publishedAt: Option[Long] = None, isLive: Option[Boolean] = None) object ImageUsages { @@ -17,7 +17,7 @@ object ImageUsages { content.id, content.webTitle, content.webUrl, - content.fields.flatMap(_.internalComposerCode.map(code => s"$composerDomain$code")), + content.fields.flatMap(_.internalComposerCode.map(code => s"${composerDomain}content/${code}")), content.fields.flatMap(_.firstPublicationDate.map(_.dateTime)), content.fields.flatMap(_.isLive) ) From 6a884caee725a3998a82b51ec5661fd0006d4317 Mon Sep 17 00:00:00 2001 From: lindseydew Date: Wed, 16 Sep 2026 14:17:14 +0100 Subject: [PATCH 5/8] Update the usages in content --- media-api/app/controllers/MediaApi.scala | 6 +++--- media-api/app/lib/ContentApi.scala | 3 +-- .../{ImageUsages.scala => UsagesInContent.scala} | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) rename media-api/app/models/{ImageUsages.scala => UsagesInContent.scala} (55%) diff --git a/media-api/app/controllers/MediaApi.scala b/media-api/app/controllers/MediaApi.scala index d374f77abb0..8f3ce3abcde 100644 --- a/media-api/app/controllers/MediaApi.scala +++ b/media-api/app/controllers/MediaApi.scala @@ -20,7 +20,7 @@ import com.sksamuel.elastic4s.requests.searches.queries.Query import lib._ import lib.elasticsearch._ import lib.querysyntax.Condition -import models.ImageUsages +import models.{ImageUsages, UsagesInContent} import org.apache.http.entity.ContentType import org.apache.pekko.stream.scaladsl.StreamConverters import org.http4s.UriTemplate @@ -189,9 +189,9 @@ class MediaApi( val composerDomain = config.composerDomain for { previewContent <- previewContentApi.findContentUsingImage(id) - previewImages = previewContent.map(sr => ImageUsages.fromSearchResponse(sr, composerDomain)) + previewImages = previewContent.map(sr => UsagesInContent.fromSearchResponse(sr, composerDomain)) } yield { - respond[List[ImageUsages]](previewImages) + respond[List[UsagesInContent]](previewImages) } } diff --git a/media-api/app/lib/ContentApi.scala b/media-api/app/lib/ContentApi.scala index 479b2a21059..9bac90beaca 100644 --- a/media-api/app/lib/ContentApi.scala +++ b/media-api/app/lib/ContentApi.scala @@ -1,9 +1,8 @@ package lib import com.gu.contentapi.client.model.v1.Content -import com.gu.contentapi.client.model.{HttpResponse, ItemQuery, SearchQuery} +import com.gu.contentapi.client.model.{HttpResponse, SearchQuery} import com.gu.contentapi.client._ -import com.gu.mediaservice.lib.config.CommonConfig import software.amazon.awssdk.auth.credentials.{AwsCredentialsProvider, ProfileCredentialsProvider} import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.sts.StsClient diff --git a/media-api/app/models/ImageUsages.scala b/media-api/app/models/UsagesInContent.scala similarity index 55% rename from media-api/app/models/ImageUsages.scala rename to media-api/app/models/UsagesInContent.scala index dc2ccb79b84..b6efc1bd1f5 100644 --- a/media-api/app/models/ImageUsages.scala +++ b/media-api/app/models/UsagesInContent.scala @@ -3,17 +3,17 @@ package models import com.gu.contentapi.client.model.v1.Content import lib.MediaApiConfig -case class ImageUsages(contentId: String, webTitle: String, webUrl: String, composerUrl: Option[String], publishedAt: Option[Long] = None, isLive: Option[Boolean] = None) +case class UsagesInContent(contentId: String, webTitle: String, webUrl: String, composerUrl: Option[String], publishedAt: Option[Long] = None, isLive: Option[Boolean] = None) -object ImageUsages { +object UsagesInContent { import play.api.libs.json._ - implicit val imageUsagesWrites: Writes[ImageUsages] = Json.writes[ImageUsages] - implicit val imageUsagesReads: Reads[ImageUsages] = Json.reads[ImageUsages] + implicit val imageUsagesWrites: Writes[UsagesInContent] = Json.writes[UsagesInContent] + implicit val imageUsagesReads: Reads[UsagesInContent] = Json.reads[UsagesInContent] def fromSearchResponse(content: Content, composerDomain: String) = { - ImageUsages( + UsagesInContent( content.id, content.webTitle, content.webUrl, From 9256035c42c9c4d7429231b2ef735ea42af470e2 Mon Sep 17 00:00:00 2001 From: lindseydew Date: Wed, 16 Sep 2026 14:20:36 +0100 Subject: [PATCH 6/8] Update the name for image takedown --- media-api/app/lib/MediaApiConfig.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media-api/app/lib/MediaApiConfig.scala b/media-api/app/lib/MediaApiConfig.scala index 39ad93deea1..2d14e6dbabb 100644 --- a/media-api/app/lib/MediaApiConfig.scala +++ b/media-api/app/lib/MediaApiConfig.scala @@ -79,7 +79,7 @@ class MediaApiConfig(resources: GridConfigResources) extends CommonConfigWithEla configuration.getOptional[Map[String, String]]("usageRestrictions").getOrElse(Map.empty) val restrictDownload: Boolean = boolean("restrictDownload") - val takedownEnabled: Boolean = booleanOpt("takedown.enabled").getOrElse(false) + val takedownEnabled: Boolean = booleanOpt("image.takedown.enabled").getOrElse(false) val queueUrl: String = stringOpt("sqs.embedder.queue.url").getOrElse("") From 3e7873ab0d4f64fff3d1ae7f8a6b71d7d2a93328 Mon Sep 17 00:00:00 2001 From: lindseydew Date: Wed, 16 Sep 2026 14:38:41 +0100 Subject: [PATCH 7/8] Add config --- dev/script/generate-config/service-config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev/script/generate-config/service-config.js b/dev/script/generate-config/service-config.js index b5820bba78e..4f8f1b49b82 100644 --- a/dev/script/generate-config/service-config.js +++ b/dev/script/generate-config/service-config.js @@ -222,6 +222,11 @@ function getMediaApiConfig(config) { |} |metrics.request.enabled=false |syndication.review.useRuntimeFieldsFix=true + |capi.preview.url="previewurl" + |capi.preview.role="previewrole" + |capi.maxRetries=3 + |capi.apiKey="apikey" + |composer.domain="composerdomain" |`; } From c3ba0472bf445ab882a5077305480a7bc632911e Mon Sep 17 00:00:00 2001 From: lindseydew Date: Wed, 16 Sep 2026 14:55:56 +0100 Subject: [PATCH 8/8] Remove the import --- media-api/app/controllers/MediaApi.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media-api/app/controllers/MediaApi.scala b/media-api/app/controllers/MediaApi.scala index 8f3ce3abcde..1106241f03e 100644 --- a/media-api/app/controllers/MediaApi.scala +++ b/media-api/app/controllers/MediaApi.scala @@ -20,7 +20,7 @@ import com.sksamuel.elastic4s.requests.searches.queries.Query import lib._ import lib.elasticsearch._ import lib.querysyntax.Condition -import models.{ImageUsages, UsagesInContent} +import models.UsagesInContent import org.apache.http.entity.ContentType import org.apache.pekko.stream.scaladsl.StreamConverters import org.http4s.UriTemplate