@financial-times/cp-content-pipeline-schema 0.7.22 → 0.7.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/lib/generated/index.d.ts +133 -32
- package/lib/helpers/isError.d.ts +1 -0
- package/lib/helpers/isError.js +7 -0
- package/lib/helpers/isError.js.map +1 -0
- package/lib/model/CapiResponse.d.ts +5 -2
- package/lib/model/CapiResponse.js +42 -4
- package/lib/model/CapiResponse.js.map +1 -1
- package/lib/model/Concept.d.ts +3 -0
- package/lib/model/Concept.js +19 -8
- package/lib/model/Concept.js.map +1 -1
- package/lib/model/Image.js +2 -4
- package/lib/model/Image.js.map +1 -1
- package/lib/model/RichText.d.ts +1 -0
- package/lib/model/RichText.js +3 -0
- package/lib/model/RichText.js.map +1 -1
- package/lib/model/RichText.test.js +2 -0
- package/lib/model/RichText.test.js.map +1 -1
- package/lib/model/Topper.d.ts +1 -0
- package/lib/model/Topper.js +3 -0
- package/lib/model/Topper.js.map +1 -1
- package/lib/model/schemas/capi/base-schema.d.ts +18 -0
- package/lib/model/schemas/capi/base-schema.js +3 -0
- package/lib/model/schemas/capi/base-schema.js.map +1 -1
- package/lib/model/schemas/capi/content-package.js +1 -0
- package/lib/model/schemas/capi/content-package.js.map +1 -1
- package/lib/resolvers/content-tree/extractText.d.ts +2 -0
- package/lib/resolvers/content-tree/extractText.js +22 -0
- package/lib/resolvers/content-tree/extractText.js.map +1 -0
- package/lib/resolvers/content-tree/references/Flourish.d.ts +4 -3
- package/lib/resolvers/content-tree/references/Flourish.js +33 -8
- package/lib/resolvers/content-tree/references/Flourish.js.map +1 -1
- package/lib/resolvers/content-tree/references/RawImage.js +2 -4
- package/lib/resolvers/content-tree/references/RawImage.js.map +1 -1
- package/lib/resolvers/content-tree/references/Tweet.js +5 -4
- package/lib/resolvers/content-tree/references/Tweet.js.map +1 -1
- package/lib/resolvers/content.d.ts +5 -1
- package/lib/resolvers/content.js +7 -1
- package/lib/resolvers/content.js.map +1 -1
- package/lib/resolvers/image.d.ts +1 -0
- package/lib/resolvers/image.js +1 -0
- package/lib/resolvers/image.js.map +1 -1
- package/lib/resolvers/index.d.ts +9 -3
- package/lib/resolvers/richText.d.ts +1 -0
- package/lib/resolvers/teaser.d.ts +1 -2
- package/lib/resolvers/teaser.js +0 -1
- package/lib/resolvers/teaser.js.map +1 -1
- package/lib/resolvers/topper.d.ts +1 -0
- package/lib/resolvers/topper.js +1 -0
- package/lib/resolvers/topper.js.map +1 -1
- package/package.json +1 -1
- package/src/generated/index.ts +90 -17
- package/src/helpers/isError.ts +3 -0
- package/src/model/CapiResponse.ts +46 -4
- package/src/model/Concept.ts +23 -10
- package/src/model/Image.ts +1 -5
- package/src/model/RichText.test.ts +2 -0
- package/src/model/RichText.ts +3 -0
- package/src/model/Topper.ts +4 -0
- package/src/model/__snapshots__/RichText.test.ts.snap +33 -0
- package/src/model/schemas/capi/base-schema.ts +3 -0
- package/src/model/schemas/capi/content-package.ts +1 -0
- package/src/resolvers/content-tree/extractText.ts +21 -0
- package/src/resolvers/content-tree/references/Flourish.ts +48 -11
- package/src/resolvers/content-tree/references/RawImage.ts +1 -4
- package/src/resolvers/content-tree/references/Tweet.ts +1 -4
- package/src/resolvers/content.ts +10 -1
- package/src/resolvers/image.ts +1 -1
- package/src/resolvers/teaser.ts +0 -1
- package/src/resolvers/topper.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/typedefs/content.graphql +21 -7
- package/typedefs/image.graphql +10 -0
- package/typedefs/richText.graphql +1 -0
- package/typedefs/teaser.graphql +0 -1
- package/typedefs/topper.graphql +7 -0
|
@@ -18,6 +18,7 @@ import sortBy from 'lodash.sortby'
|
|
|
18
18
|
|
|
19
19
|
import { CAPIImage } from './Image'
|
|
20
20
|
import { Concept } from './Concept'
|
|
21
|
+
import isError from '../helpers/isError'
|
|
21
22
|
import { uuidFromUrl } from '../helpers/metadata'
|
|
22
23
|
import { schemas } from './schemas/capi'
|
|
23
24
|
import { AccessLevel, ContentType, PackageDesign } from '../resolvers/scalars'
|
|
@@ -195,8 +196,25 @@ export class CapiResponse {
|
|
|
195
196
|
types() {
|
|
196
197
|
return this.capiData.types
|
|
197
198
|
}
|
|
198
|
-
url() {
|
|
199
|
-
|
|
199
|
+
async url(vanity?: boolean | null) {
|
|
200
|
+
const url = this.capiData.webUrl
|
|
201
|
+
if (vanity) {
|
|
202
|
+
try {
|
|
203
|
+
const vanityUrl = await this.context.dataSources.vanityUrls.get(url)
|
|
204
|
+
// IM: I don't _think_ vanityUrl will ever be nullish (instead
|
|
205
|
+
// returning the input URL if there is no vanity) but let's play it
|
|
206
|
+
// safe
|
|
207
|
+
return vanityUrl ?? url
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (isError(error)) {
|
|
210
|
+
logRecoverableError({
|
|
211
|
+
logger: this.context.logger,
|
|
212
|
+
error,
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return url
|
|
200
218
|
}
|
|
201
219
|
leadImages() {
|
|
202
220
|
if ('leadImages' in this.capiData) return this.capiData.leadImages
|
|
@@ -296,9 +314,10 @@ export class CapiResponse {
|
|
|
296
314
|
return Boolean('standout' in this.capiData && this.capiData.standout?.scoop)
|
|
297
315
|
}
|
|
298
316
|
|
|
299
|
-
relativeUrl() {
|
|
317
|
+
async relativeUrl(vanity?: boolean | null) {
|
|
300
318
|
const RELATIVE_URL_REGEX = /https?:\/\/www.ft.com/
|
|
301
|
-
|
|
319
|
+
const url = await this.url(vanity)
|
|
320
|
+
return url.replace(RELATIVE_URL_REGEX, '')
|
|
302
321
|
}
|
|
303
322
|
|
|
304
323
|
type(validate?: true): LiteralUnionScalarValues<typeof ContentType>
|
|
@@ -359,6 +378,12 @@ export class CapiResponse {
|
|
|
359
378
|
)
|
|
360
379
|
}
|
|
361
380
|
|
|
381
|
+
getGenreConcept() {
|
|
382
|
+
return this.annotations().find((annotation: Concept) =>
|
|
383
|
+
annotation.isGenreType()
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
362
387
|
getPodcastBrandConcept() {
|
|
363
388
|
return this.annotations().find((annotation: Concept) =>
|
|
364
389
|
annotation.isPodcastBrand()
|
|
@@ -421,6 +446,12 @@ export class CapiResponse {
|
|
|
421
446
|
return null
|
|
422
447
|
}
|
|
423
448
|
|
|
449
|
+
async tableOfContents() {
|
|
450
|
+
return 'tableOfContents' in this.capiData
|
|
451
|
+
? this.capiData.tableOfContents
|
|
452
|
+
: null
|
|
453
|
+
}
|
|
454
|
+
|
|
424
455
|
async liveBlogPosts(): Promise<CapiResponse[] | []> {
|
|
425
456
|
const contains = await this.contains()
|
|
426
457
|
|
|
@@ -435,6 +466,17 @@ export class CapiResponse {
|
|
|
435
466
|
return []
|
|
436
467
|
}
|
|
437
468
|
|
|
469
|
+
containedIn() {
|
|
470
|
+
if (!('containedIn' in this.capiData)) {
|
|
471
|
+
return null
|
|
472
|
+
}
|
|
473
|
+
const containerId = this.capiData.containedIn?.[0]?.id
|
|
474
|
+
if (!containerId) {
|
|
475
|
+
return null
|
|
476
|
+
}
|
|
477
|
+
return this.context.dataSources.capi.getContent(uuidFromUrl(containerId))
|
|
478
|
+
}
|
|
479
|
+
|
|
438
480
|
isContainedInPackage(): boolean {
|
|
439
481
|
return 'containedIn' in this.capiData
|
|
440
482
|
? (this.capiData.containedIn?.length ?? 0) > 0
|
package/src/model/Concept.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Annotation } from '../types/internal-content'
|
|
2
2
|
import type { QueryContext } from '..'
|
|
3
|
-
import type { DataSources } from '../datasources'
|
|
4
3
|
import { uuidFromUrl } from '../helpers/metadata'
|
|
5
4
|
import imageServiceUrl from '../helpers/imageService'
|
|
5
|
+
import isError from '../helpers/isError'
|
|
6
|
+
import { logRecoverableError } from '@dotcom-reliability-kit/log-error'
|
|
6
7
|
import conceptIds from '@financial-times/n-concept-ids'
|
|
7
8
|
|
|
8
9
|
const CAPI_ID_PREFIX = /^https?:\/\/(?:www|api)\.ft\.com\/things?\//
|
|
@@ -30,6 +31,7 @@ export const predicates = {
|
|
|
30
31
|
|
|
31
32
|
export const types = {
|
|
32
33
|
brand: 'http://www.ft.com/ontology/product/Brand',
|
|
34
|
+
genre: 'http://www.ft.com/ontology/Genre',
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export const packageBrands = [
|
|
@@ -53,11 +55,9 @@ type Genre =
|
|
|
53
55
|
| 'recipe'
|
|
54
56
|
|
|
55
57
|
export class Concept {
|
|
56
|
-
#dataSources: DataSources
|
|
57
58
|
#systemCode: string
|
|
58
59
|
|
|
59
|
-
constructor(private capiConcept: Annotation, context: QueryContext) {
|
|
60
|
-
this.#dataSources = context.dataSources
|
|
60
|
+
constructor(private capiConcept: Annotation, private context: QueryContext) {
|
|
61
61
|
this.#systemCode = context.systemCode ?? 'cp-content-pipeline'
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -100,6 +100,13 @@ export class Concept {
|
|
|
100
100
|
)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
isGenreType() {
|
|
104
|
+
return (
|
|
105
|
+
this.predicate() === predicates.isClassifiedBy &&
|
|
106
|
+
this.directType() === types.genre
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
103
110
|
isPodcastBrand() {
|
|
104
111
|
return (
|
|
105
112
|
this.predicate() === predicates.implicitlyClassifiedBy &&
|
|
@@ -136,14 +143,18 @@ export class Concept {
|
|
|
136
143
|
|
|
137
144
|
if (args?.vanity) {
|
|
138
145
|
try {
|
|
139
|
-
const vanity: string | null =
|
|
140
|
-
url
|
|
141
|
-
)
|
|
146
|
+
const vanity: string | null =
|
|
147
|
+
await this.context.dataSources.vanityUrls.get(url)
|
|
142
148
|
if (vanity !== null) {
|
|
143
149
|
url = vanity
|
|
144
150
|
}
|
|
145
|
-
} catch (
|
|
146
|
-
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (isError(error)) {
|
|
153
|
+
logRecoverableError({
|
|
154
|
+
logger: this.context.logger,
|
|
155
|
+
error,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
160
|
|
|
@@ -159,7 +170,9 @@ export class Concept {
|
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
try {
|
|
162
|
-
const peopleData = await this
|
|
173
|
+
const peopleData = await this.context.dataSources.capi.getPerson(
|
|
174
|
+
this.uuid()
|
|
175
|
+
)
|
|
163
176
|
|
|
164
177
|
if (!peopleData || !peopleData._imageUrl) return null
|
|
165
178
|
|
package/src/model/Image.ts
CHANGED
|
@@ -7,12 +7,12 @@ import type {
|
|
|
7
7
|
} from '../types/internal-content'
|
|
8
8
|
import type { QueryContext } from '..'
|
|
9
9
|
import { logRecoverableError } from '@dotcom-reliability-kit/log-error/lib'
|
|
10
|
-
import { ContentTree } from '@financial-times/content-tree'
|
|
11
10
|
import {
|
|
12
11
|
LiteralUnionScalarValues,
|
|
13
12
|
validLiteralUnionValue,
|
|
14
13
|
} from '../resolvers/literal-union'
|
|
15
14
|
import { ImageFormat, ImageType } from '../resolvers/scalars'
|
|
15
|
+
import isError from '../helpers/isError'
|
|
16
16
|
|
|
17
17
|
export type ImageSource = {
|
|
18
18
|
url: string
|
|
@@ -25,10 +25,6 @@ export type ImageSourceArgs = {
|
|
|
25
25
|
maxDpr?: number | null
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function isError(error: unknown): error is Error {
|
|
29
|
-
return error instanceof Error
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
export interface Image {
|
|
33
29
|
type(): LiteralUnionScalarValues<typeof ImageType>
|
|
34
30
|
caption(): string | null
|
|
@@ -17,6 +17,7 @@ describe('RichText resolver', () => {
|
|
|
17
17
|
expect(await model.structured()).toMatchInlineSnapshot(`
|
|
18
18
|
Object {
|
|
19
19
|
"references": Array [],
|
|
20
|
+
"text": "",
|
|
20
21
|
"tree": Object {
|
|
21
22
|
"children": Array [
|
|
22
23
|
Object {
|
|
@@ -36,6 +37,7 @@ describe('RichText resolver', () => {
|
|
|
36
37
|
expect(await model.structured()).toMatchInlineSnapshot(`
|
|
37
38
|
Object {
|
|
38
39
|
"references": Array [],
|
|
40
|
+
"text": "",
|
|
39
41
|
"tree": Object {
|
|
40
42
|
"children": Array [],
|
|
41
43
|
"type": "body",
|
package/src/model/RichText.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import bodyXMLToTree from '../resolvers/content-tree/bodyXMLToTree'
|
|
2
|
+
import extractText from '../resolvers/content-tree/extractText'
|
|
2
3
|
import updateTreeWithReferenceIds from '../resolvers/content-tree/updateTreeWithReferenceIds'
|
|
3
4
|
import { LiteralUnionScalarValues } from '../resolvers/literal-union'
|
|
4
5
|
import { RichTextSource } from '../resolvers/scalars'
|
|
@@ -45,9 +46,11 @@ export class RichText {
|
|
|
45
46
|
tree,
|
|
46
47
|
this.contentApiData
|
|
47
48
|
)
|
|
49
|
+
const text = extractText(tree)
|
|
48
50
|
|
|
49
51
|
return {
|
|
50
52
|
tree: treeWithReferences,
|
|
53
|
+
text,
|
|
51
54
|
references,
|
|
52
55
|
}
|
|
53
56
|
}
|
package/src/model/Topper.ts
CHANGED
|
@@ -97,6 +97,39 @@ Object {
|
|
|
97
97
|
},
|
|
98
98
|
},
|
|
99
99
|
],
|
|
100
|
+
"text": "Eliot After The Waste Land (Eliot Biographies, 2)by Robert Crawford, Jonathan Cape £25
|
|
101
|
+
|
|
102
|
+
After a six-year wait, Robert Crawford follows up Young Eliot (2016) with his second (and final) volume on the life of TS Eliot in time for the centenary of “The Waste Land”. Here, he explores Eliot’s marriage, religious life and draws on the 1,131 letters written to American friend Emily Hale that were only released from embargo in 2020 to probe the poet’s later years with tact and empathy.
|
|
103
|
+
|
|
104
|
+
All the Knowledge in the World: The Extraordinary History of the Encyclopaediaby Simon Garfield, Weidenfeld & Nicolson £18.99
|
|
105
|
+
|
|
106
|
+
Simon Garfield, author of quirky histories on everything from fonts to maps, surveys the publishing phenomenon that is the encyclopedia. Tracing its origins from Ancient Greece right up to its modern incarnation in Wikipedia, his handsome book offers an erudite and amusing exploration of the human quest for knowledge.
|
|
107
|
+
|
|
108
|
+
The Waste Land: A Biography of a Poemby Matthew Hollis, Faber £20
|
|
109
|
+
|
|
110
|
+
In the 100 years since TS Eliot penned his famous poem, it has taken on a life of its own. So it’s fitting, perhaps, that Matthew Hollis treats Eliot’s work to its own biography. This richly analytical book locates the poem’s genesis in the aftermath of the first world war and the “nightmare agony” of Eliot’s disastrous marriage.
|
|
111
|
+
|
|
112
|
+
Endless Flight: The Life of Joseph Rothby Keiron Pim, Granta £25
|
|
113
|
+
|
|
114
|
+
“I paint the portrait of the age,” Joseph Roth once claimed. Certainly, his work as a journalist from 1917-1939, dissecting central and eastern Europe in more than a thousand essays and anticipating the collapse of democracy on the continent as well as 19 novels provides an exceptional anatomy of a tumultuous period of history. Keiron Pim’s biography goes some way to introducing the great Austro-Hungarian writer to a new age.
|
|
115
|
+
|
|
116
|
+
Super-Infinite: The Transformations of John Donneby Katherine Rundell, Faber £16.99
|
|
117
|
+
|
|
118
|
+
Katherine Rundell, a Fellow of All Souls College, Oxford, has produced a remarkable life of John Donne, the great poet of love, sex and death. Winner of this year’s Baillie Gifford Prize, her biography of the man who was at once soldier, poet, prisoner and priest is sensitive and witty, capturing the essence of a tricky subject.
|
|
119
|
+
|
|
120
|
+
The Book of Phobias and Manias: A History of the World in 99 Obsessionsby Kate Summerscale, Profile £16.99
|
|
121
|
+
|
|
122
|
+
This neat compendium from the prizewinning author of The Suspicions of Mr Whicher charts a broad and intriguing range of fears and madness. Although these phobias and manias are alphabetised, Kate Summerscale suggests groupings (textures, animals, communal crazes) that — accompanied by a lightly erudite introduction — illuminate some of the darker corners of our collective psyche.
|
|
123
|
+
|
|
124
|
+
Papyrus: The Invention of Books in the Ancient Worldby Irene Vallejo, Hodder & Stoughton £25
|
|
125
|
+
|
|
126
|
+
Described as “a masterpiece” by Mario Vargas Llosa when it was first published in Spain in 2019, this bestselling phenomenon is now available in English. In it, Irene Vallejo recounts the birth of literary culture in the ancient world while interweaving dynamic, thrilling tales that underscore and celebrate the power of words to change the world.
|
|
127
|
+
|
|
128
|
+
Magnificent Rebels: The First Romantics and the Invention of the Selfby Andrea Wulf, John Murray £25/Knopf $35
|
|
129
|
+
|
|
130
|
+
Between 1794-1806, the cream of Germany’s intelligentsia descended on the tiny university town of Jena. There, Johann Wolfgang von Goethe mingled with philosophers Friedrich Schelling and Georg Wilhelm Friedrich Hegel, the scientist-explorer Alexander von Humboldt and the playwright Friedrich Schiller during the first flush of Romanticism. Andrea Wulf’s wonderful book brings to life the “Jena set” and a golden age of German culture.
|
|
131
|
+
|
|
132
|
+
Join our online book group on Facebook at FT Books Café",
|
|
100
133
|
"tree": Object {
|
|
101
134
|
"children": Array [
|
|
102
135
|
Object {
|
|
@@ -145,6 +145,9 @@ export const baseMetadataSchema = z.object({
|
|
|
145
145
|
.object({ apiUrl: z.string(), id: z.string() })
|
|
146
146
|
.array()
|
|
147
147
|
.optional(),
|
|
148
|
+
tableOfContents: z
|
|
149
|
+
.object({ labelType: z.string(), sequence: z.string() })
|
|
150
|
+
.optional(),
|
|
148
151
|
})
|
|
149
152
|
|
|
150
153
|
export const baseContentSchema = z.object({
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ContentTree } from '@financial-times/content-tree'
|
|
2
|
+
|
|
3
|
+
const extractTextFromNode = (
|
|
4
|
+
node: ContentTree.Paragraph | ContentTree.Phrasing
|
|
5
|
+
): string => {
|
|
6
|
+
if (node.type === 'text') {
|
|
7
|
+
return node.value
|
|
8
|
+
} else if ('children' in node) {
|
|
9
|
+
return node.children.map(extractTextFromNode).join('')
|
|
10
|
+
} else {
|
|
11
|
+
return ''
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function extractText(tree: ContentTree.Body): string {
|
|
16
|
+
return tree.children
|
|
17
|
+
.filter((node): node is ContentTree.Paragraph => node.type === 'paragraph')
|
|
18
|
+
.map(extractTextFromNode)
|
|
19
|
+
.map((paragraphText) => paragraphText.trim())
|
|
20
|
+
.join('\n\n')
|
|
21
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import imageServiceUrl from '../../../helpers/imageService'
|
|
2
2
|
import { FlourishResolvers } from '../../../generated'
|
|
3
|
+
import { QueryContext } from '../../..'
|
|
4
|
+
import isError from '../../../helpers/isError'
|
|
5
|
+
import { logRecoverableError } from '@dotcom-reliability-kit/log-error/lib'
|
|
3
6
|
|
|
4
7
|
export const Flourish = {
|
|
5
8
|
async fallbackImage(parent, _args, context) {
|
|
6
|
-
const DEFAULT_WIDTH = 2626
|
|
7
|
-
const DEFAULT_HEIGHT = 1459
|
|
8
|
-
|
|
9
9
|
const type = parent.reference.flourishType
|
|
10
10
|
const timestamp =
|
|
11
11
|
typeof parent.reference.timestamp === 'string'
|
|
@@ -15,23 +15,60 @@ export const Flourish = {
|
|
|
15
15
|
const flourishUrl = `https://public.flourish.studio/${type}/${
|
|
16
16
|
parent.reference.id
|
|
17
17
|
}/thumbnail${timestamp ? '?cacheBuster=' + timestamp : ''}`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
|
|
19
|
+
const imageMetadata = await getImageMetadata(context, flourishUrl)
|
|
20
|
+
|
|
21
|
+
const width = imageMetadata.width
|
|
22
|
+
const height = imageMetadata.height
|
|
21
23
|
|
|
22
24
|
const imageServiceWrappedUrl = imageServiceUrl({
|
|
23
25
|
url: flourishUrl,
|
|
24
26
|
systemCode: context.systemCode ?? 'cp-content-pipeline',
|
|
25
|
-
width
|
|
27
|
+
width,
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
return {
|
|
29
|
-
type: 'image',
|
|
30
31
|
url: imageServiceWrappedUrl,
|
|
31
|
-
|
|
32
|
+
type: 'image',
|
|
32
33
|
format: 'standard',
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
sourceSet: [],
|
|
35
|
+
width,
|
|
36
|
+
height,
|
|
35
37
|
}
|
|
36
38
|
},
|
|
37
39
|
} satisfies FlourishResolvers
|
|
40
|
+
|
|
41
|
+
type ImageMetadata = {
|
|
42
|
+
width: number
|
|
43
|
+
height: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const getImageMetadata = async (
|
|
47
|
+
context: QueryContext,
|
|
48
|
+
flourishUrl: string
|
|
49
|
+
): Promise<ImageMetadata> => {
|
|
50
|
+
const DEFAULT_WIDTH = 2626
|
|
51
|
+
const DEFAULT_HEIGHT = 1459
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const imageMetadata = await context.dataSources.origami.getImageMetadata(
|
|
55
|
+
flourishUrl
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
width: imageMetadata?.width || DEFAULT_WIDTH,
|
|
60
|
+
height: imageMetadata?.height || DEFAULT_HEIGHT,
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (isError(error)) {
|
|
64
|
+
logRecoverableError({
|
|
65
|
+
error,
|
|
66
|
+
logger: context.logger,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
width: DEFAULT_WIDTH,
|
|
71
|
+
height: DEFAULT_HEIGHT,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -4,10 +4,7 @@ import { logRecoverableError } from '@dotcom-reliability-kit/log-error/lib'
|
|
|
4
4
|
import imageServiceUrl from '../../../helpers/imageService'
|
|
5
5
|
import { RawImage as RawImageNode } from '../Workarounds'
|
|
6
6
|
import { RawImageResolvers } from '../../../generated'
|
|
7
|
-
|
|
8
|
-
function isError(error: unknown): error is Error {
|
|
9
|
-
return error instanceof Error
|
|
10
|
-
}
|
|
7
|
+
import isError from '../../../helpers/isError'
|
|
11
8
|
|
|
12
9
|
class RawImageModel implements Image {
|
|
13
10
|
constructor(private rawImage: RawImageNode, private context: QueryContext) {}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { logRecoverableError } from '@dotcom-reliability-kit/log-error'
|
|
2
2
|
import { TweetResolvers } from '../../../generated'
|
|
3
|
-
|
|
4
|
-
function isError(error: unknown): error is Error {
|
|
5
|
-
return error instanceof Error
|
|
6
|
-
}
|
|
3
|
+
import isError from '../../../helpers/isError'
|
|
7
4
|
|
|
8
5
|
export const Tweet = {
|
|
9
6
|
async html(parent, _args, context) {
|
package/src/resolvers/content.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Topper } from '../model/Topper'
|
|
2
2
|
import { Byline } from '../model/Byline'
|
|
3
3
|
import {
|
|
4
|
+
ArticleResolvers,
|
|
4
5
|
AudioResolvers,
|
|
5
6
|
ContentPackageResolvers,
|
|
6
7
|
ContentResolvers,
|
|
@@ -33,17 +34,24 @@ const resolvers = {
|
|
|
33
34
|
return new Byline(bylineText, authorUrlMapping).buildBylineTree()
|
|
34
35
|
},
|
|
35
36
|
url: (parent, args) =>
|
|
36
|
-
args.relative
|
|
37
|
+
args.relative
|
|
38
|
+
? parent.relativeUrl(args?.vanity)
|
|
39
|
+
: parent.url(args?.vanity),
|
|
37
40
|
type: (parent) => parent.type(),
|
|
38
41
|
mainImage: (parent) => parent.mainImage(),
|
|
39
42
|
altTitle: (parent) => parent.alternativeTitle(),
|
|
40
43
|
altStandfirst: (parent) => parent.alternativeStandfirst(),
|
|
41
44
|
publishedDate: (parent) => parent.publishedDate(),
|
|
42
45
|
annotations: (parent) => parent.annotations(),
|
|
46
|
+
accessLevel: (parent) => parent.accessLevel(),
|
|
43
47
|
commentsEnabled: (parent) => parent.commentsEnabled(),
|
|
44
48
|
design: (parent) => parent.design(),
|
|
45
49
|
},
|
|
46
50
|
|
|
51
|
+
Article: {
|
|
52
|
+
containedIn: (parent) => parent.containedIn(),
|
|
53
|
+
},
|
|
54
|
+
|
|
47
55
|
LiveBlogPackage: {
|
|
48
56
|
liveBlogPosts: (parent) => parent.liveBlogPosts(),
|
|
49
57
|
realtime: (parent) => parent.realtime(),
|
|
@@ -60,6 +68,7 @@ const resolvers = {
|
|
|
60
68
|
null,
|
|
61
69
|
},
|
|
62
70
|
} satisfies {
|
|
71
|
+
Article: ArticleResolvers
|
|
63
72
|
Content: ContentResolvers
|
|
64
73
|
LiveBlogPackage: LiveBlogPackageResolvers
|
|
65
74
|
ContentPackage: ContentPackageResolvers
|
package/src/resolvers/image.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ContentTree } from '@financial-times/content-tree'
|
|
2
1
|
import { ImageResolvers } from '../generated'
|
|
3
2
|
import { LiteralUnionScalarValues } from './literal-union'
|
|
4
3
|
import { ImageFormat } from './scalars'
|
|
@@ -60,6 +59,7 @@ const resolvers = {
|
|
|
60
59
|
width: async (image) => (await image.dimensions())?.width ?? null,
|
|
61
60
|
height: async (image) => (await image.dimensions())?.height ?? null,
|
|
62
61
|
sourceSet: (image, args) => image.sourceSet(args),
|
|
62
|
+
altText: (image) => image.alt(),
|
|
63
63
|
},
|
|
64
64
|
} satisfies { Image: ImageResolvers }
|
|
65
65
|
|
package/src/resolvers/teaser.ts
CHANGED