-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcontentful.coffee
125 lines (103 loc) · 4 KB
/
contentful.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
###
Make Contentful GraphQL adapter
###
import axios from 'axios'
# Not importing this from ./helpers because the dependency was causing issues
# when services/contentful was imported from a Nuxt module
nonEmpty = (array) -> array.filter (val) -> !!val
export getAccessToken = ->
if process.env.CONTENTFUL_PREVIEW and
previewToken = process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN
then previewToken
else process.env.CONTENTFUL_ACCESS_TOKEN
# Make a Contentful client
client = axios.create
baseURL: 'https://graphql.contentful.com/content/v1/spaces/' +
process.env.CONTENTFUL_SPACE
headers:
'Content-Type': 'application/json'
'Authorization': 'Bearer ' + getAccessToken()
# Retry requests when met with Contentful API rate limits.
# https://www.contentful.com/developers/docs/references/graphql/#/introduction/api-rate-limits
# This is based on https://github.com/compwright/axios-retry-after
# I tried using https://github.com/softonic/axios-retry but it wasn't retrying
# by default and I noticed a number of PRs for adding support for 429s. This
# requires no dependencies and is easier to understand.
client.interceptors.response.use null, (error) ->
# Check for header with amount do delay
delay = error.response?.headers?['x-contentful-ratelimit-reset']
throw error unless delay
console.log "Delaying #{delay}s for Contentful API rate limit"
# Wait the delay and re-execute. Not entirely sure why client(error.config)
# works but it does.
await new Promise (resolve) -> setTimeout resolve, delay * 1000
return client(error.config)
# Run the API query
export execute = (payload) ->
# Massage request
payload = loadPreviews payload
# Excute the query
response = await client
method: 'post'
data: payload # Should have query and optionally variables
# Return data
return response.data.data
# Use preview API to fetch draft entries
loadPreviews = (payload) ->
return payload unless process.env.CONTENTFUL_PREVIEW
return {
...payload
variables: {
preview: true
...(payload.variables || {})
}
}
# Execute a list of entries
export getEntries = (payload) ->
data = await execute payload
return flattenEntries Object.values(data)[0]?.items
# Execute a single entry and, if the query was for a collection, get the first
# item in the collection. Otherwise, it's assumed the query was for a single
# entry, so just resturn that result.
export getEntry = (payload) ->
data = await execute payload
result = Object.values(data)[0]
return flattenEntry result?.items?[0] || result
# Execute a query that gets multiple collections, and return the flattened
# collections.
export getCollections = (payload) ->
data = await execute payload
Object.keys(data).forEach (key) ->
data[key] = flattenEntries data[key]?.items
return data
# Remove empty items (like when using the non-preview GraphQL endpoint and
# there are draft entries in a reference field).
export flattenEntries = (items) ->
return [] unless items
nonEmpty(items).map flattenEntry
# Contentful nests each sub collection in an items property. This removes all
# of the items properties and adds sys.id as the id so:
# - tower.sys.id -> tower.id
# - tower.blocks.items[0].title -> tower.blocks[0].title
# - tower.blocks.items[0].sys.id -> tower.blocks[0].id
export flattenEntry = (entry) ->
Object.keys(entry).reduce (obj, key) ->
switch
# Flatten some of the sys vars onto the obj itself
when key == 'sys'
obj.id = id if id = entry.sys.id
obj.createdAt = createdAt if createdAt = entry.sys.firstPublishedAt
obj.updatedAt = updatedAt if updatedAt = entry.sys.publishedAt
# Flatten `items` and recurse through children
when (items = entry[key]?.items) and Array.isArray items
# If `items` has a `total` property, save it to `{key}Total`
# so we don't lose it. This can be used for pagination.
if (total = entry[key]?.total)?
then obj["#{key}Total"] = total
# Do the flattening
obj[key] = flattenEntries items
# Otherwise, passthrough the key/val
else obj[key] = entry[key]
# Return the entry obj
return obj
, {}