1 | 'use strict'
|
2 |
|
3 | const _ = require('lodash')
|
4 |
|
5 | const addonHeaders = function () {
|
6 | return {
|
7 | 'Accept': 'application/vnd.heroku+json; version=3.actions',
|
8 | 'Accept-Expansion': 'addon_service,plan'
|
9 | }
|
10 | }
|
11 |
|
12 | const attachmentHeaders = function () {
|
13 | return {
|
14 | 'Accept': 'application/vnd.heroku+json; version=3.actions',
|
15 | 'Accept-Inclusion': 'addon:plan,config_vars'
|
16 | }
|
17 | }
|
18 |
|
19 | const appAddon = function (heroku, app, id, options = {}) {
|
20 | const headers = addonHeaders()
|
21 | return heroku.post('/actions/addons/resolve', {
|
22 | 'headers': headers,
|
23 | 'body': {'app': app, 'addon': id, 'addon_service': options.addon_service}
|
24 | })
|
25 | .then(singularize('addon', options.namespace))
|
26 | }
|
27 |
|
28 | const handleNotFound = function (err, resource) {
|
29 | if (err.statusCode === 404 && err.body && err.body.resource === resource) {
|
30 | return true
|
31 | } else {
|
32 | throw err
|
33 | }
|
34 | }
|
35 |
|
36 | exports.appAddon = appAddon
|
37 |
|
38 | const addonResolver = function (heroku, app, id, options = {}) {
|
39 | const headers = addonHeaders()
|
40 |
|
41 | let getAddon = function (id) {
|
42 | return heroku.post('/actions/addons/resolve', {
|
43 | 'headers': headers,
|
44 | 'body': {'app': null, 'addon': id, 'addon_service': options.addon_service}
|
45 | })
|
46 | .then(singularize('addon', options.namespace))
|
47 | }
|
48 |
|
49 | if (!app || id.includes('::')) return getAddon(id)
|
50 |
|
51 | return appAddon(heroku, app, id, options)
|
52 | .catch(function (err) { if (handleNotFound(err, 'add_on')) return getAddon(id) })
|
53 | }
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | const memoizePromise = function (func, resolver) {
|
60 | var memoized = function () {
|
61 | const args = arguments
|
62 | const key = resolver.apply(this, args)
|
63 | const cache = memoized.cache
|
64 |
|
65 | if (cache.has(key)) {
|
66 | return cache.get(key)
|
67 | }
|
68 |
|
69 | const result = func.apply(this, args)
|
70 |
|
71 | return result.then(function () {
|
72 | memoized.cache = cache.set(key, result) || cache
|
73 | return result
|
74 | })
|
75 | }
|
76 | memoized.cache = new _.memoize.Cache()
|
77 | return memoized
|
78 | }
|
79 |
|
80 | exports.addon = memoizePromise(addonResolver, (_, app, id, options = {}) => `${app}|${id}|${options.addon_service}`)
|
81 |
|
82 | function NotFound () {
|
83 | Error.call(this)
|
84 | Error.captureStackTrace(this, this.constructor)
|
85 | this.name = this.constructor.name
|
86 |
|
87 | this.statusCode = 404
|
88 | this.message = 'Couldn\'t find that addon.'
|
89 | }
|
90 |
|
91 | function AmbiguousError (matches, type) {
|
92 | Error.call(this)
|
93 | Error.captureStackTrace(this, this.constructor)
|
94 | this.name = this.constructor.name
|
95 |
|
96 | this.statusCode = 422
|
97 | this.message = `Ambiguous identifier; multiple matching add-ons found: ${matches.map((match) => match.name).join(', ')}.`
|
98 | this.body = {'id': 'multiple_matches', 'message': this.message}
|
99 | this.matches = matches
|
100 | this.type = type
|
101 | }
|
102 |
|
103 | const singularize = function (type, namespace) {
|
104 | return (matches) => {
|
105 | if (namespace) {
|
106 | matches = matches.filter(m => m.namespace === namespace)
|
107 | } else if (matches.length > 1) {
|
108 |
|
109 | matches = matches.filter(m => !m.hasOwnProperty('namespace') || m.namespace === null)
|
110 | }
|
111 | switch (matches.length) {
|
112 | case 0:
|
113 | throw new NotFound()
|
114 | case 1:
|
115 | return matches[0]
|
116 | default:
|
117 | throw new AmbiguousError(matches, type)
|
118 | }
|
119 | }
|
120 | }
|
121 | exports.attachment = function (heroku, app, id, options = {}) {
|
122 | const headers = attachmentHeaders()
|
123 |
|
124 | function getAttachment (id) {
|
125 | return heroku.post('/actions/addon-attachments/resolve', {
|
126 | 'headers': headers, 'body': {'app': null, 'addon_attachment': id, 'addon_service': options.addon_service}
|
127 | }).then(singularize('addon_attachment', options.namespace))
|
128 | .catch(function (err) { handleNotFound(err, 'add_on attachment') })
|
129 | }
|
130 |
|
131 | function getAppAddonAttachment (addon, app) {
|
132 | return heroku.get(`/addons/${encodeURIComponent(addon.id)}/addon-attachments`, {headers})
|
133 | .then(filter(app, options.addon_service))
|
134 | .then(singularize('addon_attachment', options.namespace))
|
135 | }
|
136 |
|
137 | let promise
|
138 | if (!app || id.includes('::')) {
|
139 | promise = getAttachment(id)
|
140 | } else {
|
141 | promise = appAttachment(heroku, app, id, options)
|
142 | .catch(function (err) { handleNotFound(err, 'add_on attachment') })
|
143 | }
|
144 |
|
145 |
|
146 | return promise
|
147 | .then(function (attachment) {
|
148 | return {attachment}
|
149 | })
|
150 | .catch(function (error) {
|
151 | return {error}
|
152 | })
|
153 |
|
154 | .then((attachOrError) => {
|
155 | let {attachment, error} = attachOrError
|
156 |
|
157 | if (attachment) return attachment
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | else if (app) {
|
163 | return exports.addon(heroku, app, id, options)
|
164 | .then((addon) => getAppAddonAttachment(addon, app))
|
165 | .catch((addonError) => {
|
166 | if (error) throw error
|
167 | throw addonError
|
168 | })
|
169 | } else {
|
170 | if (error) throw error
|
171 | throw new NotFound()
|
172 | }
|
173 | })
|
174 | }
|
175 |
|
176 | const appAttachment = function (heroku, app, id, options = {}) {
|
177 | const headers = attachmentHeaders()
|
178 | return heroku.post('/actions/addon-attachments/resolve', {
|
179 | 'headers': headers, 'body': {'app': app, 'addon_attachment': id, 'addon_service': options.addon_service}
|
180 | }).then(singularize('addon_attachment', options.namespace))
|
181 | }
|
182 |
|
183 | exports.appAttachment = appAttachment
|
184 |
|
185 | const filter = function (app, addonService) {
|
186 | return attachments => {
|
187 | return attachments.filter(attachment => {
|
188 | if (attachment.app.name !== app) {
|
189 | return false
|
190 | }
|
191 |
|
192 | if (addonService && attachment.addon_service.name !== addonService) {
|
193 | return false
|
194 | }
|
195 |
|
196 | return true
|
197 | })
|
198 | }
|
199 | }
|