diff --git a/applications/app/controllers/ImageContentController.scala b/applications/app/controllers/ImageContentController.scala index eb9282da4e5..309e3a1f7a5 100644 --- a/applications/app/controllers/ImageContentController.scala +++ b/applications/app/controllers/ImageContentController.scala @@ -1,7 +1,7 @@ package controllers import com.gu.contentapi.client.model.{Direction, FollowingSearchQuery, SearchQuery} -import com.gu.contentapi.client.model.v1.{ItemResponse, Content => ApiContent} +import com.gu.contentapi.client.model.v1.{Block, ItemResponse, Content => ApiContent} import common._ import conf.switches.Switches import contentapi.ContentApiClient @@ -13,17 +13,17 @@ import services.ImageQuery import views.support.RenderOtherStatus import play.api.libs.json._ import conf.Configuration.contentApi +import model.dotcomrendering.{DotcomRenderingDataModel, PageType} +import renderers.DotcomRenderingService +import services.dotcomrendering.{ImageContentPicker, RemoteRender} import scala.concurrent.Future -case class ImageContentPage(image: ImageContent, related: RelatedContent) extends ContentPage { - override lazy val item: ImageContent = image -} - class ImageContentController( val contentApiClient: ContentApiClient, val controllerComponents: ControllerComponents, wsClient: WSClient, + remoteRenderer: renderers.DotcomRenderingService = DotcomRenderingService(), )(implicit context: ApplicationContext) extends BaseController with RendersItemResponse @@ -42,11 +42,42 @@ class ImageContentController( } override def renderItem(path: String)(implicit request: RequestHeader): Future[Result] = - image(Edition(request), path).map { - case Right(content) => renderImageContent(content) - case Left(result) => RenderOtherStatus(result) + image(Edition(request), path).flatMap { + case Right((content, mainBlock)) => + val tier = ImageContentPicker.getTier(content) + + tier match { + case RemoteRender => remoteRender(content, mainBlock) + case _ => Future.successful(renderImageContent(content)) + } + case Left(result) => Future.successful(RenderOtherStatus(result)) } + private def getDCRJson(content: ImageContentPage, pageType: PageType, mainBlock: Option[Block])(implicit + request: RequestHeader, + ): String = { + DotcomRenderingDataModel.toJson(DotcomRenderingDataModel.forImageContent(content, request, pageType, mainBlock)) + } + + private def remoteRender(content: ImageContentPage, mainBlock: Option[Block])(implicit + request: RequestHeader, + ): Future[Result] = { + val pageType = PageType(content, request, context) + + if (request.isJson) { + Future.successful( + common.renderJson(getDCRJson(content, pageType, mainBlock), content).as("application/json"), + ) + } else { + remoteRenderer.getImageContent( + wsClient, + content, + pageType, + mainBlock, + ) + } + } + private def isSupported(c: ApiContent) = c.isImageContent override def canRender(i: ItemResponse): Boolean = i.content.exists(isSupported) diff --git a/applications/app/controllers/InteractiveController.scala b/applications/app/controllers/InteractiveController.scala index aaf5475b1ad..a0bff41fd2d 100644 --- a/applications/app/controllers/InteractiveController.scala +++ b/applications/app/controllers/InteractiveController.scala @@ -15,6 +15,7 @@ import pages.InteractiveHtmlPage import play.api.libs.ws.WSClient import play.api.mvc._ import renderers.DotcomRenderingService +import services.dotcomrendering.{DotcomRendering, InteractivePicker, PressedInteractive} import services.{CAPILookup, USElection2020AmpPages, _} import scala.concurrent.Future diff --git a/applications/app/pages/ContentHtmlPage.scala b/applications/app/pages/ContentHtmlPage.scala index 40dba04364c..5027505fbea 100644 --- a/applications/app/pages/ContentHtmlPage.scala +++ b/applications/app/pages/ContentHtmlPage.scala @@ -1,10 +1,10 @@ package pages import common.Edition -import controllers.{ImageContentPage, MediaPage, QuizAnswersPage, TodayNewspaper} +import controllers.{MediaPage, QuizAnswersPage, TodayNewspaper} import html.HtmlPageHelpers._ import html.{HtmlPage, Styles} -import model.{ApplicationContext, Audio, Page} +import model.{ApplicationContext, Audio, ImageContentPage, Page} import play.api.mvc.RequestHeader import play.twirl.api.Html import views.html.fragments._ diff --git a/applications/app/services/ImageQuery.scala b/applications/app/services/ImageQuery.scala index fe13774c67e..1dd8b355884 100644 --- a/applications/app/services/ImageQuery.scala +++ b/applications/app/services/ImageQuery.scala @@ -1,10 +1,9 @@ package services -import com.gu.contentapi.client.model.v1.ItemResponse +import com.gu.contentapi.client.model.v1.{Block, Blocks, ItemResponse} import common.{Edition, _} import contentapi.ContentApiClient -import controllers.ImageContentPage -import model.{ApiContent2Is, ApplicationContext, Content, ImageContent, StoryPackages} +import model.{ApiContent2Is, ApplicationContext, Content, ImageContent, ImageContentPage, StoryPackages} import play.api.mvc.{RequestHeader, Result => PlayResult} import scala.concurrent.Future @@ -16,18 +15,24 @@ trait ImageQuery extends ConciergeRepository { def image(edition: Edition, path: String)(implicit request: RequestHeader, context: ApplicationContext, - ): Future[Either[PlayResult, ImageContentPage]] = { + ): Future[Either[PlayResult, (ImageContentPage, Option[Block])]] = { log.info(s"Fetching image content: $path for edition ${edition.id}") val response = contentApiClient.getResponse( contentApiClient .item(path, edition) + .showBlocks("main") .showFields("all"), ) map { response: ItemResponse => val mainContent = response.content.filter(_.isImageContent).map(Content(_)) + val mainBlock = response.content.flatMap(_.blocks).getOrElse(Blocks()).main mainContent .map { - case content: ImageContent => Right(ImageContentPage(content, StoryPackages(content.metadata.id, response))) - case _ => Left(NotFound) + case content: ImageContent => + Right( + ImageContentPage(content, StoryPackages(content.metadata.id, response)), + mainBlock, + ) + case _ => Left(NotFound) } .getOrElse(Left(NotFound)) } diff --git a/applications/app/services/dotcomrendering/ImageContentPicker.scala b/applications/app/services/dotcomrendering/ImageContentPicker.scala new file mode 100644 index 00000000000..1cb865e3d64 --- /dev/null +++ b/applications/app/services/dotcomrendering/ImageContentPicker.scala @@ -0,0 +1,48 @@ +package services.dotcomrendering + +import common.GuLogging +import experiments.{ActiveExperiments, DCRImageContent} +import model.Cors.RichRequestHeader +import model.ImageContentPage +import play.api.mvc.RequestHeader +import utils.DotcomponentsLogger + +object ImageContentPicker extends GuLogging { + + /** + * + * Add to this function any logic for including/excluding + * an image article from being rendered with DCR + * + * Currently defaulting to false until we implement image articles in DCR + * + * */ + private def dcrCouldRender(imageContentPage: ImageContentPage): Boolean = { + false + } + + def getTier( + imageContentPage: ImageContentPage, + )(implicit + request: RequestHeader, + ): RenderType = { + + val participatingInTest = ActiveExperiments.isParticipating(DCRImageContent) + val dcrCanRender = dcrCouldRender(imageContentPage) + + val tier = { + if (request.forceDCROff) LocalRender + else if (request.forceDCR) RemoteRender + else if (dcrCanRender && participatingInTest) RemoteRender + else LocalRender + } + + if (tier == RemoteRender) { + DotcomponentsLogger.logger.logRequest(s"path executing in dotcomponents", Map.empty, imageContentPage.image) + } else { + DotcomponentsLogger.logger.logRequest(s"path executing in web", Map.empty, imageContentPage.image) + } + + tier + } +} diff --git a/applications/app/services/InteractivePicker.scala b/applications/app/services/dotcomrendering/InteractivePicker.scala similarity index 94% rename from applications/app/services/InteractivePicker.scala rename to applications/app/services/dotcomrendering/InteractivePicker.scala index 4a7a1a77c60..41be91f7e61 100644 --- a/applications/app/services/InteractivePicker.scala +++ b/applications/app/services/dotcomrendering/InteractivePicker.scala @@ -1,9 +1,8 @@ -package services +package services.dotcomrendering import conf.switches.Switches.InteractivePickerFeature -import play.api.mvc.RequestHeader import implicits.Requests._ -import services.dotcomrendering.PressedContent +import play.api.mvc.RequestHeader sealed trait RenderingTier object DotcomRendering extends RenderingTier diff --git a/applications/app/services/dotcomrendering/RenderType.scala b/applications/app/services/dotcomrendering/RenderType.scala new file mode 100644 index 00000000000..367fc080f45 --- /dev/null +++ b/applications/app/services/dotcomrendering/RenderType.scala @@ -0,0 +1,5 @@ +package services.dotcomrendering + +sealed trait RenderType +case object RemoteRender extends RenderType +case object LocalRender extends RenderType diff --git a/applications/app/views/fragments/imageContentBody.scala.html b/applications/app/views/fragments/imageContentBody.scala.html index 85ed385f8ea..1e2b77b6df4 100644 --- a/applications/app/views/fragments/imageContentBody.scala.html +++ b/applications/app/views/fragments/imageContentBody.scala.html @@ -1,3 +1,4 @@ +@import model.ImageContentPage @(page: ImageContentPage)(implicit request: RequestHeader, context: model.ApplicationContext) @import layout.ContentWidths.ImageContentMedia @import views.support.Commercial._ diff --git a/applications/test/services/InteractivePickerTest.scala b/applications/test/services/InteractivePickerTest.scala index 962f578ab53..823cb9d9cc5 100644 --- a/applications/test/services/InteractivePickerTest.scala +++ b/applications/test/services/InteractivePickerTest.scala @@ -4,6 +4,7 @@ import org.scalatest.DoNotDiscover import test.TestRequest import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import services.dotcomrendering.{DotcomRendering, FrontendLegacy, InteractivePicker, PressedInteractive} @DoNotDiscover class InteractivePickerTest extends AnyFlatSpec with Matchers { val path = "/lifeandstyle/ng-interactive/2016/mar/12/stephen-collins-cats-cartoon" diff --git a/article/app/controllers/LiveBlogController.scala b/article/app/controllers/LiveBlogController.scala index e179943fd40..d993d677e71 100644 --- a/article/app/controllers/LiveBlogController.scala +++ b/article/app/controllers/LiveBlogController.scala @@ -17,9 +17,9 @@ import play.api.libs.ws.WSClient import play.api.mvc._ import play.twirl.api.Html import renderers.DotcomRenderingService -import services.dotcomrendering.DotcomponentsLogger import services.{CAPILookup, NewsletterService, MessageUsService} import topics.TopicService +import utils.DotcomponentsLogger import views.support.RenderOtherStatus import scala.concurrent.Future @@ -191,7 +191,7 @@ class LiveBlogController( if (remoteRendering) { DotcomponentsLogger.logger - .logRequest(s"liveblog executing in dotcomponents", properties, page) + .logRequest(s"liveblog executing in dotcomponents", properties, page.article) val pageType: PageType = PageType(blog, request, context) remoteRenderer.getArticle( ws, @@ -206,7 +206,7 @@ class LiveBlogController( messageUs, ) } else { - DotcomponentsLogger.logger.logRequest(s"liveblog executing in web", properties, page) + DotcomponentsLogger.logger.logRequest(s"liveblog executing in web", properties, page.article) Future.successful(common.renderHtml(LiveBlogHtmlPage.html(blog), blog)) } case (blog: LiveBlogPage, AmpFormat) if isAmpSupported => diff --git a/article/app/services/dotcomrendering/ArticlePicker.scala b/article/app/services/dotcomrendering/ArticlePicker.scala index 758bfbddc6a..86a4228452a 100644 --- a/article/app/services/dotcomrendering/ArticlePicker.scala +++ b/article/app/services/dotcomrendering/ArticlePicker.scala @@ -4,6 +4,7 @@ import com.madgag.scala.collection.decorators.MapDecorator import implicits.Requests._ import model.{ArticlePage, PageWithStoryPackage} import play.api.mvc.RequestHeader +import utils.DotcomponentsLogger object ArticlePageChecks { @@ -69,11 +70,11 @@ object ArticlePicker { ("pageTones" -> pageTones) if (tier == RemoteRender) { - DotcomponentsLogger.logger.logRequest(s"path executing in dotcomponents", features, page) + DotcomponentsLogger.logger.logRequest(s"path executing in dotcomponents", features, page.article) } else if (tier == PressedArticle) { - DotcomponentsLogger.logger.logRequest(s"path executing from pressed content", features, page) + DotcomponentsLogger.logger.logRequest(s"path executing from pressed content", features, page.article) } else { - DotcomponentsLogger.logger.logRequest(s"path executing in web", features, page) + DotcomponentsLogger.logger.logRequest(s"path executing in web", features, page.article) } tier diff --git a/common/app/experiments/Experiments.scala b/common/app/experiments/Experiments.scala index 8492e3586a0..34540eb3878 100644 --- a/common/app/experiments/Experiments.scala +++ b/common/app/experiments/Experiments.scala @@ -40,6 +40,15 @@ object DCRFronts participationGroup = Perc50, ) +object DCRImageContent + extends Experiment( + name = "dcr-image-content", + description = "Use DCR for image content", + owners = Seq(Owner.withGithub("@guardian/dotcom-platform")), + sellByDate = LocalDate.of(2024, 1, 1), + participationGroup = Perc0E, + ) + object OfferHttp3 extends Experiment( name = "offer-http3", diff --git a/common/app/model/ImageContentPage.scala b/common/app/model/ImageContentPage.scala new file mode 100644 index 00000000000..6dd3db7d923 --- /dev/null +++ b/common/app/model/ImageContentPage.scala @@ -0,0 +1,5 @@ +package model + +case class ImageContentPage(image: ImageContent, related: RelatedContent) extends ContentPage { + override lazy val item: ImageContent = image +} diff --git a/common/app/model/dotcomrendering/DotcomRenderingDataModel.scala b/common/app/model/dotcomrendering/DotcomRenderingDataModel.scala index fa72fc60172..10dd3b0ade7 100644 --- a/common/app/model/dotcomrendering/DotcomRenderingDataModel.scala +++ b/common/app/model/dotcomrendering/DotcomRenderingDataModel.scala @@ -17,12 +17,13 @@ import model.{ ContentFormat, ContentPage, GUDateTimeFormatNew, + ImageContentPage, InteractivePage, LiveBlogPage, + MessageUsData, PageWithStoryPackage, Topic, TopicResult, - MessageUsData, } import navigation._ import play.api.libs.json._ @@ -257,6 +258,31 @@ object DotcomRenderingDataModel { ) } + def forImageContent( + imageContentPage: ImageContentPage, + request: RequestHeader, + pageType: PageType, + mainBlock: Option[APIBlock], + ) = { + + val linkedData = LinkedData.forArticle( + article = imageContentPage.image, + baseURL = Configuration.dotcom.baseUrl, + fallbackLogo = Configuration.images.fallbackLogo, + ) + + apply( + page = imageContentPage, + request = request, + pageType = pageType, + linkedData = linkedData, + mainBlock = mainBlock, + bodyBlocks = Seq.empty, + hasStoryPackage = imageContentPage.related.hasStoryPackage, + storyPackage = getStoryPackage(imageContentPage.related.faciaItems, request), + ) + } + def keyEventsFallback( blocks: APIBlocks, ): Seq[APIBlock] = { @@ -321,46 +347,46 @@ object DotcomRenderingDataModel { val mostRecentBlockId = getMostRecentBlockId(blocks) apply( - page, - request, - pagination, - linkedData, - blocks.main, - bodyBlocks, - pageType, - page.related.hasStoryPackage, - getStoryPackage(page.related.faciaItems, request), //todo - pinnedPost, - timelineBlocks, - filterKeyEvents, - mostRecentBlockId, - forceLive, - availableTopics, - newsletter, - topicResult, - messageUs, + page = page, + request = request, + pagination = pagination, + linkedData = linkedData, + mainBlock = blocks.main, + bodyBlocks = bodyBlocks, + pageType = pageType, + hasStoryPackage = page.related.hasStoryPackage, + storyPackage = getStoryPackage(page.related.faciaItems, request), //todo + pinnedPost = pinnedPost, + keyEvents = timelineBlocks, + filterKeyEvents = filterKeyEvents, + mostRecentBlockId = mostRecentBlockId, + forceLive = forceLive, + availableTopics = availableTopics, + newsletter = newsletter, + topicResult = topicResult, + messageUs = messageUs, ) } def apply( page: ContentPage, request: RequestHeader, - pagination: Option[Pagination], + pageType: PageType, linkedData: List[LinkedData], - mainBlock: Option[APIBlock], bodyBlocks: Seq[APIBlock], - pageType: PageType, // TODO remove as format is better - hasStoryPackage: Boolean, - storyPackage: Option[OnwardCollectionResponse], - pinnedPost: Option[APIBlock], - keyEvents: Seq[APIBlock], + mainBlock: Option[APIBlock] = None, + hasStoryPackage: Boolean = false, + storyPackage: Option[OnwardCollectionResponse] = None, + newsletter: Option[NewsletterData] = None, + keyEvents: Seq[APIBlock] = Seq.empty, + pagination: Option[Pagination] = None, + pinnedPost: Option[APIBlock] = None, + availableTopics: Option[Seq[Topic]] = None, + topicResult: Option[TopicResult] = None, + messageUs: Option[MessageUsData] = None, filterKeyEvents: Boolean = false, mostRecentBlockId: Option[String] = None, forceLive: Boolean = false, - availableTopics: Option[Seq[Topic]], - newsletter: Option[NewsletterData], - topicResult: Option[TopicResult], - messageUs: Option[MessageUsData], ): DotcomRenderingDataModel = { val edition = Edition.edition(request) diff --git a/common/app/model/dotcomrendering/LinkedData.scala b/common/app/model/dotcomrendering/LinkedData.scala index 11df741b3a3..203ed4af8dd 100644 --- a/common/app/model/dotcomrendering/LinkedData.scala +++ b/common/app/model/dotcomrendering/LinkedData.scala @@ -1,7 +1,7 @@ package model.dotcomrendering import com.gu.contentapi.client.model.v1.{Block => CAPIBlock} -import model.{Article, ImageMedia, Interactive, LiveBlogPage, Tags} +import model.{Article, ContentType, ImageMedia, Interactive, LiveBlogPage, Tags} import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat import play.api.libs.functional.syntax._ @@ -79,7 +79,7 @@ object LinkedData { } def forArticle( - article: Article, + article: ContentType, baseURL: String, fallbackLogo: String, ): List[LinkedData] = { @@ -144,7 +144,7 @@ object LinkedData { } private[this] def getImagesForArticle( - article: Article, + article: ContentType, fallbackLogo: String, ): List[String] = getImages(article.trail.trailPicture, article.content.openGraphImage, fallbackLogo) diff --git a/common/app/renderers/DotcomRenderingService.scala b/common/app/renderers/DotcomRenderingService.scala index 5d21d672f4e..3f6e829ac06 100644 --- a/common/app/renderers/DotcomRenderingService.scala +++ b/common/app/renderers/DotcomRenderingService.scala @@ -8,31 +8,30 @@ import conf.Configuration import conf.switches.Switches.CircuitBreakerSwitch import http.{HttpPreconnections, ResultWithPreconnectPreload} import model.Cached.{RevalidatableResult, WithoutRevalidationResult} -import model.{SimplePage} -import model.dotcomrendering.{ - DotcomBlocksRenderingDataModel, - DotcomFrontsRenderingDataModel, - DotcomNewslettersPageRenderingDataModel, - DotcomRenderingDataModel, - OnwardCollectionResponse, - PageType, -} -import services.NewsletterData -import services.newsletters.model.NewsletterResponse - import model.{ CacheTime, Cached, + ImageContentPage, InteractivePage, LiveBlogPage, + MessageUsData, NoCache, PageWithStoryPackage, PressedPage, RelatedContentItem, + SimplePage, Topic, TopicResult, - MessageUsData, } +import model.dotcomrendering.{ + DotcomBlocksRenderingDataModel, + DotcomFrontsRenderingDataModel, + DotcomNewslettersPageRenderingDataModel, + DotcomRenderingDataModel, + PageType, +} +import services.NewsletterData +import services.newsletters.model.NewsletterResponse import play.api.libs.ws.{WSClient, WSResponse} import play.api.mvc.Results.{InternalServerError, NotFound} import play.api.mvc.{RequestHeader, Result} @@ -44,7 +43,6 @@ import java.util.concurrent.TimeoutException import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.concurrent.duration._ -import model.dotcomrendering.Trail // Introduced as CAPI error handling elsewhere would smother these otherwise case class DCRLocalConnectException(message: String) extends ConnectException(message) @@ -319,6 +317,17 @@ class DotcomRenderingService extends GuLogging with ResultWithPreconnectPreload val json = DotcomNewslettersPageRenderingDataModel.toJson(dataModel) post(ws, json, Configuration.rendering.baseURL + "/EmailNewsletters", CacheTime.Facia) } + + def getImageContent( + ws: WSClient, + imageContent: ImageContentPage, + pageType: PageType, + mainBlock: Option[Block], + )(implicit request: RequestHeader): Future[Result] = { + val dataModel = DotcomRenderingDataModel.forImageContent(imageContent, request, pageType, mainBlock) + val json = DotcomRenderingDataModel.toJson(dataModel) + post(ws, json, Configuration.rendering.baseURL + "/Article", CacheTime.Facia) + } } object DotcomRenderingService { diff --git a/article/app/services/dotcomrendering/DotcomponentsLogger.scala b/common/app/utils/DotcomponentsLogger.scala similarity index 87% rename from article/app/services/dotcomrendering/DotcomponentsLogger.scala rename to common/app/utils/DotcomponentsLogger.scala index b3c95a21689..4d67b17a269 100644 --- a/article/app/services/dotcomrendering/DotcomponentsLogger.scala +++ b/common/app/utils/DotcomponentsLogger.scala @@ -1,8 +1,8 @@ -package services.dotcomrendering +package utils import common.GuLogging import common.LoggingField._ -import model.PageWithStoryPackage +import model.{ContentPage, ContentType, PageWithStoryPackage} import model.liveblog.InteractiveBlockElement import play.api.mvc.RequestHeader @@ -35,21 +35,22 @@ case class DotcomponentsLogger(request: Option[RequestHeader]) extends GuLogging def fieldsFromResults(results: Map[String, String]): List[LogField] = results.map({ case (k, v) => LogFieldString(k, v) }).toList - def elementsLogFieldFromPage(page: PageWithStoryPackage): List[LogField] = { + def elementsLogFieldFromPage(content: ContentType): List[LogField] = { + val bodyBlocks = for { - blocks <- page.article.blocks.toSeq + blocks <- content.fields.blocks.toSeq body <- blocks.body element <- body.elements } yield element.getClass.getSimpleName val mainBlocks = for { - blocks <- page.article.blocks.toSeq + blocks <- content.fields.blocks.toSeq main <- blocks.main.toSeq element <- main.elements } yield element.getClass.getSimpleName val bodyInteractiveBlockScripts = for { - blocks <- page.article.blocks.toSeq + blocks <- content.fields.blocks.toSeq body <- blocks.body element <- body.elements if element.isInstanceOf[InteractiveBlockElement] interactiveElement <- Try(element.asInstanceOf[InteractiveBlockElement]).toOption @@ -67,7 +68,7 @@ case class DotcomponentsLogger(request: Option[RequestHeader]) extends GuLogging ), LogFieldString( "page.tone", - page.article.tags.tones.headOption.map(_.name).getOrElse(""), + content.tags.tones.headOption.map(_.name).getOrElse(""), ), LogFieldString( "page.bodyInteractiveElementScripts", @@ -76,7 +77,7 @@ case class DotcomponentsLogger(request: Option[RequestHeader]) extends GuLogging ) } - def logRequest(msg: String, results: Map[String, String], page: PageWithStoryPackage)(implicit + def logRequest(msg: String, results: Map[String, String], page: ContentType)(implicit request: RequestHeader, ): Unit = { withRequestHeaders(request).results(msg, results, page) @@ -86,7 +87,7 @@ case class DotcomponentsLogger(request: Option[RequestHeader]) extends GuLogging copy(Some(rh)) } - def results(message: String, results: Map[String, String], page: PageWithStoryPackage): Unit = { + def results(message: String, results: Map[String, String], page: ContentType): Unit = { logInfoWithCustomFields(message, customFields ++ fieldsFromResults(results) ++ elementsLogFieldFromPage(page)) } diff --git a/data/database/813d0e61f5db52cbab4b9773dfe1c5418e0df523045fa77c94ed0290771d8c08 b/data/database/813d0e61f5db52cbab4b9773dfe1c5418e0df523045fa77c94ed0290771d8c08 new file mode 100644 index 00000000000..196d3298fe8 Binary files /dev/null and b/data/database/813d0e61f5db52cbab4b9773dfe1c5418e0df523045fa77c94ed0290771d8c08 differ diff --git a/data/database/9ccf29b2568987676711c6a162d04d900f8fbe988ef9736deecb484095be8483 b/data/database/9ccf29b2568987676711c6a162d04d900f8fbe988ef9736deecb484095be8483 new file mode 100644 index 00000000000..b744c24796b Binary files /dev/null and b/data/database/9ccf29b2568987676711c6a162d04d900f8fbe988ef9736deecb484095be8483 differ