@budibase/backend-core 2.24.2 → 2.25.0

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.
@@ -13,13 +13,14 @@ import { bucketTTLConfig, budibaseTempDir } from "./utils"
13
13
  import { v4 } from "uuid"
14
14
  import { APP_PREFIX, APP_DEV_PREFIX } from "../db"
15
15
  import fsp from "fs/promises"
16
+ import { HeadObjectOutput } from "aws-sdk/clients/s3"
16
17
 
17
18
  const streamPipeline = promisify(stream.pipeline)
18
19
  // use this as a temporary store of buckets that are being created
19
20
  const STATE = {
20
21
  bucketCreationPromises: {},
21
22
  }
22
- const signedFilePrefix = "/files/signed"
23
+ export const SIGNED_FILE_PREFIX = "/files/signed"
23
24
 
24
25
  type ListParams = {
25
26
  ContinuationToken?: string
@@ -40,8 +41,13 @@ type UploadParams = BaseUploadParams & {
40
41
  path?: string | PathLike
41
42
  }
42
43
 
43
- type StreamUploadParams = BaseUploadParams & {
44
- stream: ReadStream
44
+ export type StreamTypes =
45
+ | ReadStream
46
+ | NodeJS.ReadableStream
47
+ | ReadableStream<Uint8Array>
48
+
49
+ export type StreamUploadParams = BaseUploadParams & {
50
+ stream?: StreamTypes
45
51
  }
46
52
 
47
53
  const CONTENT_TYPE_MAP: any = {
@@ -174,11 +180,9 @@ export async function upload({
174
180
  const objectStore = ObjectStore(bucketName)
175
181
  const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
176
182
 
177
- if (ttl && (bucketCreated.created || bucketCreated.exists)) {
183
+ if (ttl && bucketCreated.created) {
178
184
  let ttlConfig = bucketTTLConfig(bucketName, ttl)
179
- if (objectStore.putBucketLifecycleConfiguration) {
180
- await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
181
- }
185
+ await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
182
186
  }
183
187
 
184
188
  let contentType = type
@@ -222,11 +226,9 @@ export async function streamUpload({
222
226
  const objectStore = ObjectStore(bucketName)
223
227
  const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
224
228
 
225
- if (ttl && (bucketCreated.created || bucketCreated.exists)) {
229
+ if (ttl && bucketCreated.created) {
226
230
  let ttlConfig = bucketTTLConfig(bucketName, ttl)
227
- if (objectStore.putBucketLifecycleConfiguration) {
228
- await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
229
- }
231
+ await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
230
232
  }
231
233
 
232
234
  // Set content type for certain known extensions
@@ -333,7 +335,7 @@ export function getPresignedUrl(
333
335
  const signedUrl = new URL(url)
334
336
  const path = signedUrl.pathname
335
337
  const query = signedUrl.search
336
- return `${signedFilePrefix}${path}${query}`
338
+ return `${SIGNED_FILE_PREFIX}${path}${query}`
337
339
  }
338
340
  }
339
341
 
@@ -521,6 +523,26 @@ export async function getReadStream(
521
523
  return client.getObject(params).createReadStream()
522
524
  }
523
525
 
526
+ export async function getObjectMetadata(
527
+ bucket: string,
528
+ path: string
529
+ ): Promise<HeadObjectOutput> {
530
+ bucket = sanitizeBucket(bucket)
531
+ path = sanitizeKey(path)
532
+
533
+ const client = ObjectStore(bucket)
534
+ const params = {
535
+ Bucket: bucket,
536
+ Key: path,
537
+ }
538
+
539
+ try {
540
+ return await client.headObject(params).promise()
541
+ } catch (err: any) {
542
+ throw new Error("Unable to retrieve metadata from object")
543
+ }
544
+ }
545
+
524
546
  /*
525
547
  Given a signed url like '/files/signed/tmp-files-attachments/app_123456/myfile.txt' extract
526
548
  the bucket and the path from it
@@ -530,7 +552,9 @@ export function extractBucketAndPath(
530
552
  ): { bucket: string; path: string } | null {
531
553
  const baseUrl = url.split("?")[0]
532
554
 
533
- const regex = new RegExp(`^${signedFilePrefix}/(?<bucket>[^/]+)/(?<path>.+)$`)
555
+ const regex = new RegExp(
556
+ `^${SIGNED_FILE_PREFIX}/(?<bucket>[^/]+)/(?<path>.+)$`
557
+ )
534
558
  const match = baseUrl.match(regex)
535
559
 
536
560
  if (match && match.groups) {
@@ -1,9 +1,14 @@
1
- import { join } from "path"
1
+ import path, { join } from "path"
2
2
  import { tmpdir } from "os"
3
3
  import fs from "fs"
4
4
  import env from "../environment"
5
5
  import { PutBucketLifecycleConfigurationRequest } from "aws-sdk/clients/s3"
6
-
6
+ import * as objectStore from "./objectStore"
7
+ import {
8
+ AutomationAttachment,
9
+ AutomationAttachmentContent,
10
+ BucketedContent,
11
+ } from "@budibase/types"
7
12
  /****************************************************
8
13
  * NOTE: When adding a new bucket - name *
9
14
  * sure that S3 usages (like budibase-infra) *
@@ -55,3 +60,50 @@ export const bucketTTLConfig = (
55
60
 
56
61
  return params
57
62
  }
63
+
64
+ async function processUrlAttachment(
65
+ attachment: AutomationAttachment
66
+ ): Promise<AutomationAttachmentContent> {
67
+ const response = await fetch(attachment.url)
68
+ if (!response.ok || !response.body) {
69
+ throw new Error(`Unexpected response ${response.statusText}`)
70
+ }
71
+ const fallbackFilename = path.basename(new URL(attachment.url).pathname)
72
+ return {
73
+ filename: attachment.filename || fallbackFilename,
74
+ content: response.body,
75
+ }
76
+ }
77
+
78
+ export async function processObjectStoreAttachment(
79
+ attachment: AutomationAttachment
80
+ ): Promise<BucketedContent> {
81
+ const result = objectStore.extractBucketAndPath(attachment.url)
82
+
83
+ if (result === null) {
84
+ throw new Error("Invalid signed URL")
85
+ }
86
+
87
+ const { bucket, path: objectPath } = result
88
+ const readStream = await objectStore.getReadStream(bucket, objectPath)
89
+ const fallbackFilename = path.basename(objectPath)
90
+ return {
91
+ bucket,
92
+ path: objectPath,
93
+ filename: attachment.filename || fallbackFilename,
94
+ content: readStream,
95
+ }
96
+ }
97
+
98
+ export async function processAutomationAttachment(
99
+ attachment: AutomationAttachment
100
+ ): Promise<AutomationAttachmentContent | BucketedContent> {
101
+ const isFullyFormedUrl =
102
+ attachment.url?.startsWith("http://") ||
103
+ attachment.url?.startsWith("https://")
104
+ if (isFullyFormedUrl) {
105
+ return await processUrlAttachment(attachment)
106
+ } else {
107
+ return await processObjectStoreAttachment(attachment)
108
+ }
109
+ }