Skip to content

Commit c83338f

Browse files
authored
Merge branch 'main' into ld/java-sdk-iam
2 parents fd4bb2c + 9c2d6e1 commit c83338f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2004
-703
lines changed

.github/dependabot.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Please see the documentation for all configuration options:
2+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3+
4+
version: 2
5+
updates:
6+
- package-ecosystem: "npm"
7+
directories:
8+
- "/"
9+
- "/pluto-message-ingestion"
10+
schedule:
11+
interval: "weekly"
12+
cooldown:
13+
default-days: 5
14+
semver-major-days: 30
15+
semver-minor-days: 7
16+
semver-patch-days: 3
17+
- package-ecosystem: "github-actions"
18+
directory: "/"
19+
schedule:
20+
interval: "weekly"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: CORE4 label enforcement
2+
permissions:
3+
contents: read
4+
on:
5+
pull_request:
6+
types: [opened, labeled, unlabeled, synchronize, reopened, edited]
7+
jobs:
8+
require-label:
9+
uses: guardian/.github/.github/workflows/require-label.yaml@4cb5024736632ffcc564b7f4b772c38b8e5ce739 # v2.0.0

app/controllers/Api.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ class Api(
5151
search: Option[String],
5252
limit: Option[Int],
5353
shouldUseCreatedDateForSort: Boolean,
54-
mediaPlatform: Option[String]
54+
mediaPlatform: Option[String],
55+
orderByOldest: Boolean
5556
) = APIAuthAction {
5657
val atoms = stores.atomListStore.getAtoms(
5758
search,
5859
limit,
5960
shouldUseCreatedDateForSort,
60-
mediaPlatform
61+
mediaPlatform,
62+
orderByOldest
6163
)
6264
Ok(Json.toJson(atoms))
6365
}

app/controllers/UploadController.scala

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,29 +99,23 @@ class UploadController(
9999
latestAssets.sortBy(ClientAsset.getVersion).reverse
100100
}
101101

102-
def create: Action[AnyContent] = LookupPermissions { implicit raw =>
102+
def create: Action[AnyContent] = APIAuthAction { implicit raw =>
103103
parse(raw) { req: UploadRequest =>
104-
if (req.selfHost && !raw.permissions.addSelfHostedAsset) {
105-
Unauthorized(
106-
s"User ${raw.user.email} is not authorised with permissions to upload self-hosted asset"
107-
)
108-
} else {
109-
log.info(
110-
s"Request for upload under atom ${req.atomId}. filename=${req.filename}. size=${req.size}, selfHosted=${req.selfHost}"
111-
)
104+
log.info(
105+
s"Request for upload under atom ${req.atomId}. filename=${req.filename}. size=${req.size}, selfHosted=${req.selfHost}"
106+
)
112107

113-
val thriftAtom = getPreviewAtom(req.atomId)
114-
val atom = MediaAtom.fromThrift(thriftAtom)
115-
val assetVersion =
116-
MediaAtomHelpers.getNextAssetVersion(thriftAtom.tdata)
108+
val thriftAtom = getPreviewAtom(req.atomId)
109+
val atom = MediaAtom.fromThrift(thriftAtom)
110+
val assetVersion =
111+
MediaAtomHelpers.getNextAssetVersion(thriftAtom.tdata)
117112

118-
val upload = start(atom, raw.user.email, req, assetVersion)
113+
val upload = start(atom, raw.user.email, req, assetVersion)
119114

120-
log.info(
121-
s"Upload created under atom ${req.atomId}. upload=${upload.id}. parts=${upload.parts.size}, selfHosted=${upload.metadata.selfHost}"
122-
)
123-
Ok(Json.toJson(upload))
124-
}
115+
log.info(
116+
s"Upload created under atom ${req.atomId}. upload=${upload.id}. parts=${upload.parts.size}, selfHosted=${upload.metadata.selfHost}"
117+
)
118+
Ok(Json.toJson(upload))
125119
}
126120
}
127121

app/data/AtomListStore.scala

Lines changed: 117 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,66 @@ package data
22

33
import com.gu.atom.data.PreviewDynamoDataStoreV2
44
import com.gu.media.CapiAccess
5-
import com.gu.media.model.{ContentChangeDetails, Image, MediaAtom}
5+
import com.gu.media.model.Platform.{Url, Youtube}
6+
import com.gu.media.model.VideoPlayerFormat.Loop
7+
import com.gu.media.model.{
8+
ContentChangeDetails,
9+
Image,
10+
MediaAtom,
11+
Platform,
12+
VideoPlayerFormat
13+
}
614
import com.gu.media.util.TestFilters
715
import model.commands.CommandExceptions.AtomDataStoreError
816
import model.{MediaAtomList, MediaAtomSummary}
17+
import play.api.Logging
918
import play.api.libs.json.{JsArray, JsValue}
1019

1120
trait AtomListStore {
1221
def getAtoms(
1322
search: Option[String],
1423
limit: Option[Int],
1524
shouldUseCreatedDateForSort: Boolean,
16-
mediaPlatform: Option[String]
25+
platformFilter: Option[String],
26+
orderByOldest: Boolean
1727
): MediaAtomList
1828
}
1929

20-
class CapiBackedAtomListStore(capi: CapiAccess) extends AtomListStore {
30+
class CapiBackedAtomListStore(capi: CapiAccess)
31+
extends AtomListStore
32+
with Logging {
33+
34+
// CAPI max page size is 200
35+
val CapiMaxPageSize = 200
36+
2137
override def getAtoms(
2238
search: Option[String],
2339
limit: Option[Int],
2440
shouldUseCreatedDateForSort: Boolean,
25-
mediaPlatform: Option[String]
41+
platformFilter: Option[String],
42+
orderByOldest: Boolean
2643
): MediaAtomList = {
27-
// CAPI max page size is 200
28-
val cappedLimit: Option[Int] = limit.map(Math.min(200, _))
44+
val pagination = Pagination.option(CapiMaxPageSize, limit)
2945

3046
val dateSorter = shouldUseCreatedDateForSort match {
3147
case true => Map("order-date" -> "first-publication")
3248
case false => Map.empty
3349
}
3450

35-
val mediaPlatformFilter = mediaPlatform match {
51+
val mediaPlatformFilter = platformFilter match {
3652
case Some(mPlatform) => Map("media-platform" -> mPlatform)
3753
case _ => Map.empty
3854
}
3955

40-
val base: Map[String, String] = Map(
41-
"types" -> "media",
42-
"order-by" -> "newest"
43-
) ++
56+
val orderBy = orderByOldest match {
57+
case true => Map("order-by" -> "oldest")
58+
case false => Map("order-by" -> "newest")
59+
}
60+
61+
val base: Map[String, String] = Map("types" -> "media") ++
4462
dateSorter ++
45-
mediaPlatformFilter
63+
mediaPlatformFilter ++
64+
orderBy
4665

4766
val baseWithSearch = search match {
4867
case Some(q) =>
@@ -53,20 +72,57 @@ class CapiBackedAtomListStore(capi: CapiAccess) extends AtomListStore {
5372
case None => base
5473
}
5574

56-
val baseWithSearchAndLimit = cappedLimit match {
57-
case Some(pageSize) =>
75+
val baseWithSearchAndLimit = pagination match {
76+
case Some(Pagination(pageSize, _)) =>
5877
baseWithSearch ++ Map(
5978
"page-size" -> pageSize.toString
6079
)
6180
case None => baseWithSearch
6281
}
6382

64-
val response = capi.capiQuery("atoms", baseWithSearchAndLimit)
83+
val nPages = pagination.map(_.pageCount).getOrElse(1)
84+
85+
val (total, _, atoms) =
86+
(1 to nPages).foldLeft(0, nPages, List.empty[MediaAtomSummary]) {
87+
case ((prevTotal, prevMaxPage, prevAtoms), page) =>
88+
val pageNumber = pagination match {
89+
case Some(_) => Map("page" -> page.toString)
90+
case None => Map.empty
91+
}
92+
// make sure we don't request beyond the last page
93+
val (total, maxPage, atoms) = if (page <= prevMaxPage) {
94+
getCapiAtoms(baseWithSearchAndLimit ++ pageNumber)
95+
} else {
96+
(prevTotal, prevMaxPage, Nil)
97+
}
98+
(
99+
total,
100+
maxPage,
101+
prevAtoms ++ atoms
102+
)
103+
}
104+
105+
val limitedAtoms = limit match {
106+
case Some(limit) => atoms.take(limit)
107+
case None => atoms
108+
}
109+
110+
logger.info(s"total $total, atoms: ${limitedAtoms.size}")
111+
MediaAtomList(total, limitedAtoms)
112+
}
65113

114+
private[data] def getCapiAtoms(
115+
query: Map[String, String]
116+
): (Int, Int, List[MediaAtomSummary]) = {
117+
val response = capi.capiQuery("atoms", query)
66118
val total = (response \ "response" \ "total").as[Int]
119+
val maxPage = (response \ "response" \ "pages").as[Int]
67120
val results = (response \ "response" \ "results").as[JsArray]
68-
69-
MediaAtomList(total, results.value.flatMap(fromJson).toList)
121+
(
122+
total,
123+
maxPage,
124+
results.value.flatMap(fromJson).toList
125+
)
70126
}
71127

72128
private def fromJson(wrapper: JsValue): Option[MediaAtomSummary] = {
@@ -94,38 +150,42 @@ class CapiBackedAtomListStore(capi: CapiAccess) extends AtomListStore {
94150
(asset \ "version").as[Long]
95151
}
96152

97-
val mediaPlatforms = (atom \ "assets")
98-
.as[JsArray]
99-
.value
100-
.flatMap { asset =>
101-
(asset \ "platform").asOpt[String].map(_.toLowerCase)
102-
}
103-
.toList
104-
.distinct
153+
val atomPlatform =
154+
(atom \ "platform").asOpt[Platform]
105155

106-
val currentAsset = (atom \ "assets").as[JsArray].value.find { asset =>
156+
val activeAsset = (atom \ "assets").as[JsArray].value.find { asset =>
107157
val assetVersion = (asset \ "version").as[Long]
108158
activeVersion.contains(assetVersion)
109159
}
110160

111-
val currentMediaPlatform = currentAsset.flatMap { asset =>
112-
(asset \ "platform").asOpt[String].map(_.toLowerCase)
161+
val activeAssetPlatform = activeAsset.map { asset =>
162+
(asset \ "platform").as[Platform]
113163
}
114164

115-
// sort media platforms so the current one is first
116-
val sortedMediaPlatforms = currentMediaPlatform match {
117-
case Some(current) => current :: mediaPlatforms.filter(_ != current)
118-
case None => mediaPlatforms
119-
}
165+
val firstAssetPlatform =
166+
(atom \ "assets").as[JsArray].value.headOption.map { asset =>
167+
(asset \ "platform").as[Platform]
168+
}
169+
170+
val platform = Platform.getPlatform(
171+
atomPlatform,
172+
activeAssetPlatform,
173+
firstAssetPlatform
174+
)
175+
176+
val videoPlayerFormat =
177+
(atom \ "metadata" \ "selfHost" \ "videoPlayerFormat")
178+
.asOpt[VideoPlayerFormat]
179+
.orElse(if (platform == Url) Some(Loop) else None)
120180

121181
Some(
122182
MediaAtomSummary(
123183
id,
124184
title,
125185
posterImage,
126186
contentChangeDetails,
127-
sortedMediaPlatforms,
128-
currentMediaPlatform
187+
platform,
188+
videoPlayerFormat
129189
)
130190
)
131191
}
@@ -138,7 +198,8 @@ class DynamoBackedAtomListStore(store: PreviewDynamoDataStoreV2)
138198
search: Option[String],
139199
limit: Option[Int],
140200
shouldUseCreatedDateForSort: Boolean,
141-
mediaPlatform: Option[String]
201+
mediaPlatform: Option[String],
202+
orderByOldest: Boolean
142203
): MediaAtomList = {
143204
// We must filter the entire list of atoms rather than use Dynamo limit to ensure stable iteration order.
144205
// Without it, the front page will shuffle around when clicking the Load More button.
@@ -157,7 +218,11 @@ class DynamoBackedAtomListStore(store: PreviewDynamoDataStoreV2)
157218
.map(MediaAtom.fromThrift)
158219
.toList
159220
.sortBy(sortField(_).map(_.date.getMillis))
160-
.reverse // newest atoms first
221+
222+
val mediaAtomsSorted = orderByOldest match {
223+
case true => mediaAtoms
224+
case false => mediaAtoms.reverse
225+
}
161226

162227
val searchTermFilter = search match {
163228
case Some(str) => Some((atom: MediaAtom) => atom.title.contains(str))
@@ -177,7 +242,7 @@ class DynamoBackedAtomListStore(store: PreviewDynamoDataStoreV2)
177242
val filters = List(searchTermFilter, mediaPlatformFilter).flatten
178243

179244
val filteredAtoms =
180-
filters.foldLeft(mediaAtoms)((atoms, f) => atoms.filter(f))
245+
filters.foldLeft(mediaAtomsSorted)((atoms, f) => atoms.filter(f))
181246

182247
val limitedAtoms = limit match {
183248
case Some(l) => filteredAtoms.take(l)
@@ -189,27 +254,13 @@ class DynamoBackedAtomListStore(store: PreviewDynamoDataStoreV2)
189254
}
190255

191256
private def fromAtom(atom: MediaAtom): MediaAtomSummary = {
192-
val versions = atom.assets.map(_.version).toSet
193-
val currentAsset = atom.assets.find(asset =>
194-
asset.version == atom.activeVersion.getOrElse(versions.max)
195-
)
196-
val mediaPlatforms = atom.assets.map(_.platform.name.toLowerCase).distinct
197-
val currentMediaPlatform =
198-
currentAsset.map(_.platform.name).map(_.toLowerCase)
199-
200-
// sort media platforms so the current one is first
201-
val sortedMediaPlatforms = currentMediaPlatform match {
202-
case Some(current) => current :: mediaPlatforms.filter(_ != current)
203-
case None => mediaPlatforms
204-
}
205-
206257
MediaAtomSummary(
207258
atom.id,
208259
atom.title,
209260
atom.posterImage,
210261
atom.contentChangeDetails,
211-
sortedMediaPlatforms,
212-
currentMediaPlatform
262+
atom.platform.getOrElse(Youtube),
263+
atom.videoPlayerFormat
213264
)
214265
}
215266
}
@@ -225,3 +276,15 @@ object AtomListStore {
225276
case _ => new CapiBackedAtomListStore(capi)
226277
}
227278
}
279+
280+
case class Pagination(pageSize: Int, pageCount: Int)
281+
282+
object Pagination {
283+
def option(maxPageSize: Int, limit: Option[Int]): Option[Pagination] = {
284+
limit.map { limit =>
285+
val pageSize = Math.min(maxPageSize, limit)
286+
val pageCount = Math.ceil(1.0 * limit / maxPageSize).toInt
287+
Pagination(pageSize, pageCount)
288+
}
289+
}
290+
}

app/di.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,6 @@ class MediaAtomMaker(context: Context)
128128

129129
private val youTube = YouTube(config, Duration.ofDays(1), youtubeCredentials)
130130

131-
private val uploaderMessageConsumer = PlutoMessageConsumer(stores, aws)
132-
uploaderMessageConsumer.start(actorSystem.scheduler)(actorSystem.dispatcher)
133-
134131
private val thumbnailGenerator = ThumbnailGenerator(
135132
environment.getFile(s"conf/logo.png")
136133
)

0 commit comments

Comments
 (0)