@budibase/backend-core 2.11.43 → 2.11.44
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/dist/index.js +372 -344
- package/dist/index.js.map +3 -3
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +4 -4
- package/dist/src/objectStore/buckets/app.d.ts +7 -10
- package/dist/src/objectStore/buckets/app.js +40 -27
- package/dist/src/objectStore/buckets/app.js.map +1 -1
- package/dist/src/objectStore/buckets/plugins.d.ts +4 -4
- package/dist/src/objectStore/buckets/plugins.js +19 -19
- package/dist/src/objectStore/buckets/plugins.js.map +1 -1
- package/dist/src/objectStore/objectStore.d.ts +19 -16
- package/dist/src/objectStore/objectStore.js +257 -218
- package/dist/src/objectStore/objectStore.js.map +1 -1
- package/package.json +4 -4
- package/src/objectStore/buckets/app.ts +36 -23
- package/src/objectStore/buckets/plugins.ts +8 -8
- package/src/objectStore/buckets/tests/app.spec.ts +16 -26
- package/src/objectStore/objectStore.ts +38 -24
|
@@ -1,37 +1,50 @@
|
|
|
1
1
|
import env from "../../environment"
|
|
2
2
|
import * as objectStore from "../objectStore"
|
|
3
3
|
import * as cloudfront from "../cloudfront"
|
|
4
|
+
import qs from "querystring"
|
|
5
|
+
import { DEFAULT_TENANT_ID, getTenantId } from "../../context"
|
|
6
|
+
|
|
7
|
+
export function clientLibraryPath(appId: string) {
|
|
8
|
+
return `${objectStore.sanitizeKey(appId)}/budibase-client.js`
|
|
9
|
+
}
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @param appId In production we need the appId to look up the correct bucket, as the
|
|
10
|
-
* version of the client lib may differ between apps.
|
|
11
|
-
* @param version The version to retrieve.
|
|
12
|
-
* @return The URL to be inserted into appPackage response or server rendered
|
|
13
|
-
* app index file.
|
|
12
|
+
* Previously we used to serve the client library directly from Cloudfront, however
|
|
13
|
+
* due to issues with the domain we were unable to continue doing this - keeping
|
|
14
|
+
* incase we are able to switch back to CDN path again in future.
|
|
14
15
|
*/
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
file += `?v=${version}`
|
|
22
|
-
}
|
|
23
|
-
// don't need to use presigned for client with cloudfront
|
|
24
|
-
// file is public
|
|
25
|
-
return cloudfront.getUrl(file)
|
|
26
|
-
} else {
|
|
27
|
-
return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
|
|
16
|
+
export function clientLibraryCDNUrl(appId: string, version: string) {
|
|
17
|
+
let file = clientLibraryPath(appId)
|
|
18
|
+
if (env.CLOUDFRONT_CDN) {
|
|
19
|
+
// append app version to bust the cache
|
|
20
|
+
if (version) {
|
|
21
|
+
file += `?v=${version}`
|
|
28
22
|
}
|
|
23
|
+
// don't need to use presigned for client with cloudfront
|
|
24
|
+
// file is public
|
|
25
|
+
return cloudfront.getUrl(file)
|
|
29
26
|
} else {
|
|
30
|
-
return
|
|
27
|
+
return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function clientLibraryUrl(appId: string, version: string) {
|
|
32
|
+
let tenantId, qsParams: { appId: string; version: string; tenantId?: string }
|
|
33
|
+
try {
|
|
34
|
+
tenantId = getTenantId()
|
|
35
|
+
} finally {
|
|
36
|
+
qsParams = {
|
|
37
|
+
appId,
|
|
38
|
+
version,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (tenantId && tenantId !== DEFAULT_TENANT_ID) {
|
|
42
|
+
qsParams.tenantId = tenantId
|
|
31
43
|
}
|
|
44
|
+
return `/api/assets/client?${qs.encode(qsParams)}`
|
|
32
45
|
}
|
|
33
46
|
|
|
34
|
-
export
|
|
47
|
+
export function getAppFileUrl(s3Key: string) {
|
|
35
48
|
if (env.CLOUDFRONT_CDN) {
|
|
36
49
|
return cloudfront.getPresignedUrl(s3Key)
|
|
37
50
|
} else {
|
|
@@ -6,7 +6,7 @@ import { Plugin } from "@budibase/types"
|
|
|
6
6
|
|
|
7
7
|
// URLS
|
|
8
8
|
|
|
9
|
-
export
|
|
9
|
+
export function enrichPluginURLs(plugins: Plugin[]) {
|
|
10
10
|
if (!plugins || !plugins.length) {
|
|
11
11
|
return []
|
|
12
12
|
}
|
|
@@ -17,12 +17,12 @@ export const enrichPluginURLs = (plugins: Plugin[]) => {
|
|
|
17
17
|
})
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
function getPluginJSUrl(plugin: Plugin) {
|
|
21
21
|
const s3Key = getPluginJSKey(plugin)
|
|
22
22
|
return getPluginUrl(s3Key)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
function getPluginIconUrl(plugin: Plugin): string | undefined {
|
|
26
26
|
const s3Key = getPluginIconKey(plugin)
|
|
27
27
|
if (!s3Key) {
|
|
28
28
|
return
|
|
@@ -30,7 +30,7 @@ const getPluginIconUrl = (plugin: Plugin): string | undefined => {
|
|
|
30
30
|
return getPluginUrl(s3Key)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
function getPluginUrl(s3Key: string) {
|
|
34
34
|
if (env.CLOUDFRONT_CDN) {
|
|
35
35
|
return cloudfront.getPresignedUrl(s3Key)
|
|
36
36
|
} else {
|
|
@@ -40,11 +40,11 @@ const getPluginUrl = (s3Key: string) => {
|
|
|
40
40
|
|
|
41
41
|
// S3 KEYS
|
|
42
42
|
|
|
43
|
-
export
|
|
43
|
+
export function getPluginJSKey(plugin: Plugin) {
|
|
44
44
|
return getPluginS3Key(plugin, "plugin.min.js")
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export
|
|
47
|
+
export function getPluginIconKey(plugin: Plugin) {
|
|
48
48
|
// stored iconUrl is deprecated - hardcode to icon.svg in this case
|
|
49
49
|
const iconFileName = plugin.iconUrl ? "icon.svg" : plugin.iconFileName
|
|
50
50
|
if (!iconFileName) {
|
|
@@ -53,12 +53,12 @@ export const getPluginIconKey = (plugin: Plugin) => {
|
|
|
53
53
|
return getPluginS3Key(plugin, iconFileName)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
function getPluginS3Key(plugin: Plugin, fileName: string) {
|
|
57
57
|
const s3Key = getPluginS3Dir(plugin.name)
|
|
58
58
|
return `${s3Key}/${fileName}`
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export
|
|
61
|
+
export function getPluginS3Dir(pluginName: string) {
|
|
62
62
|
let s3Key = `${pluginName}`
|
|
63
63
|
if (env.MULTI_TENANCY) {
|
|
64
64
|
const tenantId = context.getTenantId()
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as app from "../app"
|
|
2
|
-
import { getAppFileUrl } from "../app"
|
|
3
2
|
import { testEnv } from "../../../../tests/extra"
|
|
4
3
|
|
|
5
4
|
describe("app", () => {
|
|
@@ -7,6 +6,15 @@ describe("app", () => {
|
|
|
7
6
|
testEnv.nodeJest()
|
|
8
7
|
})
|
|
9
8
|
|
|
9
|
+
function baseCheck(url: string, tenantId?: string) {
|
|
10
|
+
expect(url).toContain("/api/assets/client")
|
|
11
|
+
if (tenantId) {
|
|
12
|
+
expect(url).toContain(`tenantId=${tenantId}`)
|
|
13
|
+
}
|
|
14
|
+
expect(url).toContain("appId=app_123")
|
|
15
|
+
expect(url).toContain("version=2.0.0")
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
describe("clientLibraryUrl", () => {
|
|
11
19
|
function getClientUrl() {
|
|
12
20
|
return app.clientLibraryUrl("app_123/budibase-client.js", "2.0.0")
|
|
@@ -20,31 +28,19 @@ describe("app", () => {
|
|
|
20
28
|
it("gets url in dev", () => {
|
|
21
29
|
testEnv.nodeDev()
|
|
22
30
|
const url = getClientUrl()
|
|
23
|
-
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it("gets url with embedded minio", () => {
|
|
27
|
-
testEnv.withMinio()
|
|
28
|
-
const url = getClientUrl()
|
|
29
|
-
expect(url).toBe(
|
|
30
|
-
"/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
|
|
31
|
-
)
|
|
31
|
+
baseCheck(url)
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
it("gets url with custom S3", () => {
|
|
35
35
|
testEnv.withS3()
|
|
36
36
|
const url = getClientUrl()
|
|
37
|
-
|
|
38
|
-
"http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
|
|
39
|
-
)
|
|
37
|
+
baseCheck(url)
|
|
40
38
|
})
|
|
41
39
|
|
|
42
40
|
it("gets url with cloudfront + s3", () => {
|
|
43
41
|
testEnv.withCloudfront()
|
|
44
42
|
const url = getClientUrl()
|
|
45
|
-
|
|
46
|
-
"http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0"
|
|
47
|
-
)
|
|
43
|
+
baseCheck(url)
|
|
48
44
|
})
|
|
49
45
|
})
|
|
50
46
|
|
|
@@ -57,7 +53,7 @@ describe("app", () => {
|
|
|
57
53
|
testEnv.nodeDev()
|
|
58
54
|
await testEnv.withTenant(tenantId => {
|
|
59
55
|
const url = getClientUrl()
|
|
60
|
-
|
|
56
|
+
baseCheck(url, tenantId)
|
|
61
57
|
})
|
|
62
58
|
})
|
|
63
59
|
|
|
@@ -65,9 +61,7 @@ describe("app", () => {
|
|
|
65
61
|
await testEnv.withTenant(tenantId => {
|
|
66
62
|
testEnv.withMinio()
|
|
67
63
|
const url = getClientUrl()
|
|
68
|
-
|
|
69
|
-
"/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
|
|
70
|
-
)
|
|
64
|
+
baseCheck(url, tenantId)
|
|
71
65
|
})
|
|
72
66
|
})
|
|
73
67
|
|
|
@@ -75,9 +69,7 @@ describe("app", () => {
|
|
|
75
69
|
await testEnv.withTenant(tenantId => {
|
|
76
70
|
testEnv.withS3()
|
|
77
71
|
const url = getClientUrl()
|
|
78
|
-
|
|
79
|
-
"http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
|
|
80
|
-
)
|
|
72
|
+
baseCheck(url, tenantId)
|
|
81
73
|
})
|
|
82
74
|
})
|
|
83
75
|
|
|
@@ -85,9 +77,7 @@ describe("app", () => {
|
|
|
85
77
|
await testEnv.withTenant(tenantId => {
|
|
86
78
|
testEnv.withCloudfront()
|
|
87
79
|
const url = getClientUrl()
|
|
88
|
-
|
|
89
|
-
"http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0"
|
|
90
|
-
)
|
|
80
|
+
baseCheck(url, tenantId)
|
|
91
81
|
})
|
|
92
82
|
})
|
|
93
83
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const sanitize = require("sanitize-s3-objectkey")
|
|
2
2
|
import AWS from "aws-sdk"
|
|
3
|
-
import stream from "stream"
|
|
3
|
+
import stream, { Readable } from "stream"
|
|
4
4
|
import fetch from "node-fetch"
|
|
5
5
|
import tar from "tar-fs"
|
|
6
6
|
import zlib from "zlib"
|
|
@@ -66,10 +66,10 @@ export function sanitizeBucket(input: string) {
|
|
|
66
66
|
* @return an S3 object store object, check S3 Nodejs SDK for usage.
|
|
67
67
|
* @constructor
|
|
68
68
|
*/
|
|
69
|
-
export
|
|
69
|
+
export function ObjectStore(
|
|
70
70
|
bucket: string,
|
|
71
71
|
opts: { presigning: boolean } = { presigning: false }
|
|
72
|
-
)
|
|
72
|
+
) {
|
|
73
73
|
const config: any = {
|
|
74
74
|
s3ForcePathStyle: true,
|
|
75
75
|
signatureVersion: "v4",
|
|
@@ -104,7 +104,7 @@ export const ObjectStore = (
|
|
|
104
104
|
* Given an object store and a bucket name this will make sure the bucket exists,
|
|
105
105
|
* if it does not exist then it will create it.
|
|
106
106
|
*/
|
|
107
|
-
export
|
|
107
|
+
export async function makeSureBucketExists(client: any, bucketName: string) {
|
|
108
108
|
bucketName = sanitizeBucket(bucketName)
|
|
109
109
|
try {
|
|
110
110
|
await client
|
|
@@ -139,13 +139,13 @@ export const makeSureBucketExists = async (client: any, bucketName: string) => {
|
|
|
139
139
|
* Uploads the contents of a file given the required parameters, useful when
|
|
140
140
|
* temp files in use (for example file uploaded as an attachment).
|
|
141
141
|
*/
|
|
142
|
-
export
|
|
142
|
+
export async function upload({
|
|
143
143
|
bucket: bucketName,
|
|
144
144
|
filename,
|
|
145
145
|
path,
|
|
146
146
|
type,
|
|
147
147
|
metadata,
|
|
148
|
-
}: UploadParams)
|
|
148
|
+
}: UploadParams) {
|
|
149
149
|
const extension = filename.split(".").pop()
|
|
150
150
|
const fileBytes = fs.readFileSync(path)
|
|
151
151
|
|
|
@@ -180,12 +180,12 @@ export const upload = async ({
|
|
|
180
180
|
* Similar to the upload function but can be used to send a file stream
|
|
181
181
|
* through to the object store.
|
|
182
182
|
*/
|
|
183
|
-
export
|
|
183
|
+
export async function streamUpload(
|
|
184
184
|
bucketName: string,
|
|
185
185
|
filename: string,
|
|
186
186
|
stream: any,
|
|
187
187
|
extra = {}
|
|
188
|
-
)
|
|
188
|
+
) {
|
|
189
189
|
const objectStore = ObjectStore(bucketName)
|
|
190
190
|
await makeSureBucketExists(objectStore, bucketName)
|
|
191
191
|
|
|
@@ -215,7 +215,7 @@ export const streamUpload = async (
|
|
|
215
215
|
* retrieves the contents of a file from the object store, if it is a known content type it
|
|
216
216
|
* will be converted, otherwise it will be returned as a buffer stream.
|
|
217
217
|
*/
|
|
218
|
-
export
|
|
218
|
+
export async function retrieve(bucketName: string, filepath: string) {
|
|
219
219
|
const objectStore = ObjectStore(bucketName)
|
|
220
220
|
const params = {
|
|
221
221
|
Bucket: sanitizeBucket(bucketName),
|
|
@@ -230,7 +230,7 @@ export const retrieve = async (bucketName: string, filepath: string) => {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
export
|
|
233
|
+
export async function listAllObjects(bucketName: string, path: string) {
|
|
234
234
|
const objectStore = ObjectStore(bucketName)
|
|
235
235
|
const list = (params: ListParams = {}) => {
|
|
236
236
|
return objectStore
|
|
@@ -261,11 +261,11 @@ export const listAllObjects = async (bucketName: string, path: string) => {
|
|
|
261
261
|
/**
|
|
262
262
|
* Generate a presigned url with a default TTL of 1 hour
|
|
263
263
|
*/
|
|
264
|
-
export
|
|
264
|
+
export function getPresignedUrl(
|
|
265
265
|
bucketName: string,
|
|
266
266
|
key: string,
|
|
267
267
|
durationSeconds: number = 3600
|
|
268
|
-
)
|
|
268
|
+
) {
|
|
269
269
|
const objectStore = ObjectStore(bucketName, { presigning: true })
|
|
270
270
|
const params = {
|
|
271
271
|
Bucket: sanitizeBucket(bucketName),
|
|
@@ -291,7 +291,7 @@ export const getPresignedUrl = (
|
|
|
291
291
|
/**
|
|
292
292
|
* Same as retrieval function but puts to a temporary file.
|
|
293
293
|
*/
|
|
294
|
-
export
|
|
294
|
+
export async function retrieveToTmp(bucketName: string, filepath: string) {
|
|
295
295
|
bucketName = sanitizeBucket(bucketName)
|
|
296
296
|
filepath = sanitizeKey(filepath)
|
|
297
297
|
const data = await retrieve(bucketName, filepath)
|
|
@@ -300,7 +300,7 @@ export const retrieveToTmp = async (bucketName: string, filepath: string) => {
|
|
|
300
300
|
return outputPath
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
export
|
|
303
|
+
export async function retrieveDirectory(bucketName: string, path: string) {
|
|
304
304
|
let writePath = join(budibaseTempDir(), v4())
|
|
305
305
|
fs.mkdirSync(writePath)
|
|
306
306
|
const objects = await listAllObjects(bucketName, path)
|
|
@@ -324,7 +324,7 @@ export const retrieveDirectory = async (bucketName: string, path: string) => {
|
|
|
324
324
|
/**
|
|
325
325
|
* Delete a single file.
|
|
326
326
|
*/
|
|
327
|
-
export
|
|
327
|
+
export async function deleteFile(bucketName: string, filepath: string) {
|
|
328
328
|
const objectStore = ObjectStore(bucketName)
|
|
329
329
|
await makeSureBucketExists(objectStore, bucketName)
|
|
330
330
|
const params = {
|
|
@@ -334,7 +334,7 @@ export const deleteFile = async (bucketName: string, filepath: string) => {
|
|
|
334
334
|
return objectStore.deleteObject(params).promise()
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
export
|
|
337
|
+
export async function deleteFiles(bucketName: string, filepaths: string[]) {
|
|
338
338
|
const objectStore = ObjectStore(bucketName)
|
|
339
339
|
await makeSureBucketExists(objectStore, bucketName)
|
|
340
340
|
const params = {
|
|
@@ -349,10 +349,10 @@ export const deleteFiles = async (bucketName: string, filepaths: string[]) => {
|
|
|
349
349
|
/**
|
|
350
350
|
* Delete a path, including everything within.
|
|
351
351
|
*/
|
|
352
|
-
export
|
|
352
|
+
export async function deleteFolder(
|
|
353
353
|
bucketName: string,
|
|
354
354
|
folder: string
|
|
355
|
-
): Promise<any>
|
|
355
|
+
): Promise<any> {
|
|
356
356
|
bucketName = sanitizeBucket(bucketName)
|
|
357
357
|
folder = sanitizeKey(folder)
|
|
358
358
|
const client = ObjectStore(bucketName)
|
|
@@ -383,11 +383,11 @@ export const deleteFolder = async (
|
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
export
|
|
386
|
+
export async function uploadDirectory(
|
|
387
387
|
bucketName: string,
|
|
388
388
|
localPath: string,
|
|
389
389
|
bucketPath: string
|
|
390
|
-
)
|
|
390
|
+
) {
|
|
391
391
|
bucketName = sanitizeBucket(bucketName)
|
|
392
392
|
let uploads = []
|
|
393
393
|
const files = fs.readdirSync(localPath, { withFileTypes: true })
|
|
@@ -404,11 +404,11 @@ export const uploadDirectory = async (
|
|
|
404
404
|
return files
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
export
|
|
407
|
+
export async function downloadTarballDirect(
|
|
408
408
|
url: string,
|
|
409
409
|
path: string,
|
|
410
410
|
headers = {}
|
|
411
|
-
)
|
|
411
|
+
) {
|
|
412
412
|
path = sanitizeKey(path)
|
|
413
413
|
const response = await fetch(url, { headers })
|
|
414
414
|
if (!response.ok) {
|
|
@@ -418,11 +418,11 @@ export const downloadTarballDirect = async (
|
|
|
418
418
|
await streamPipeline(response.body, zlib.createUnzip(), tar.extract(path))
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
export
|
|
421
|
+
export async function downloadTarball(
|
|
422
422
|
url: string,
|
|
423
423
|
bucketName: string,
|
|
424
424
|
path: string
|
|
425
|
-
)
|
|
425
|
+
) {
|
|
426
426
|
bucketName = sanitizeBucket(bucketName)
|
|
427
427
|
path = sanitizeKey(path)
|
|
428
428
|
const response = await fetch(url)
|
|
@@ -438,3 +438,17 @@ export const downloadTarball = async (
|
|
|
438
438
|
// return the temporary path incase there is a use for it
|
|
439
439
|
return tmpPath
|
|
440
440
|
}
|
|
441
|
+
|
|
442
|
+
export async function getReadStream(
|
|
443
|
+
bucketName: string,
|
|
444
|
+
path: string
|
|
445
|
+
): Promise<Readable> {
|
|
446
|
+
bucketName = sanitizeBucket(bucketName)
|
|
447
|
+
path = sanitizeKey(path)
|
|
448
|
+
const client = ObjectStore(bucketName)
|
|
449
|
+
const params = {
|
|
450
|
+
Bucket: bucketName,
|
|
451
|
+
Key: path,
|
|
452
|
+
}
|
|
453
|
+
return client.getObject(params).createReadStream()
|
|
454
|
+
}
|