@budibase/backend-core 3.2.4 → 3.2.6
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.map +1 -1
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +11 -4
- package/dist/plugins.js.meta.json +1 -1
- package/package.json +11 -4
- package/src/accounts/accounts.ts +0 -82
- package/src/accounts/api.ts +0 -59
- package/src/accounts/index.ts +0 -1
- package/src/auth/auth.ts +0 -210
- package/src/auth/index.ts +0 -1
- package/src/auth/tests/auth.spec.ts +0 -14
- package/src/blacklist/blacklist.ts +0 -54
- package/src/blacklist/index.ts +0 -1
- package/src/blacklist/tests/blacklist.spec.ts +0 -46
- package/src/cache/appMetadata.ts +0 -88
- package/src/cache/base/index.ts +0 -150
- package/src/cache/docWritethrough.ts +0 -105
- package/src/cache/generic.ts +0 -33
- package/src/cache/index.ts +0 -8
- package/src/cache/invite.ts +0 -86
- package/src/cache/passwordReset.ts +0 -49
- package/src/cache/tests/docWritethrough.spec.ts +0 -296
- package/src/cache/tests/user.spec.ts +0 -145
- package/src/cache/tests/writethrough.spec.ts +0 -139
- package/src/cache/user.ts +0 -154
- package/src/cache/writethrough.ts +0 -133
- package/src/configs/configs.ts +0 -263
- package/src/configs/index.ts +0 -1
- package/src/configs/tests/configs.spec.ts +0 -184
- package/src/constants/db.ts +0 -75
- package/src/constants/index.ts +0 -2
- package/src/constants/misc.ts +0 -36
- package/src/context/Context.ts +0 -14
- package/src/context/identity.ts +0 -58
- package/src/context/index.ts +0 -3
- package/src/context/mainContext.ts +0 -422
- package/src/context/tests/index.spec.ts +0 -255
- package/src/context/types.ts +0 -26
- package/src/db/Replication.ts +0 -94
- package/src/db/couch/DatabaseImpl.ts +0 -511
- package/src/db/couch/connections.ts +0 -89
- package/src/db/couch/index.ts +0 -4
- package/src/db/couch/pouchDB.ts +0 -97
- package/src/db/couch/pouchDump.ts +0 -0
- package/src/db/couch/tests/DatabaseImpl.spec.ts +0 -118
- package/src/db/couch/utils.ts +0 -55
- package/src/db/db.ts +0 -34
- package/src/db/errors.ts +0 -14
- package/src/db/index.ts +0 -12
- package/src/db/instrumentation.ts +0 -199
- package/src/db/lucene.ts +0 -721
- package/src/db/searchIndexes/index.ts +0 -1
- package/src/db/searchIndexes/searchIndexes.ts +0 -62
- package/src/db/tests/DatabaseImpl.spec.ts +0 -55
- package/src/db/tests/connections.spec.ts +0 -22
- package/src/db/tests/index.spec.ts +0 -32
- package/src/db/tests/lucene.spec.ts +0 -400
- package/src/db/tests/pouch.spec.js +0 -62
- package/src/db/tests/utils.spec.ts +0 -63
- package/src/db/utils.ts +0 -208
- package/src/db/views.ts +0 -245
- package/src/docIds/conversions.ts +0 -60
- package/src/docIds/ids.ts +0 -126
- package/src/docIds/index.ts +0 -2
- package/src/docIds/newid.ts +0 -5
- package/src/docIds/params.ts +0 -189
- package/src/docUpdates/index.ts +0 -24
- package/src/environment.ts +0 -293
- package/src/errors/errors.ts +0 -119
- package/src/errors/index.ts +0 -1
- package/src/events/analytics.ts +0 -6
- package/src/events/asyncEvents/index.ts +0 -2
- package/src/events/asyncEvents/publisher.ts +0 -12
- package/src/events/asyncEvents/queue.ts +0 -22
- package/src/events/backfill.ts +0 -183
- package/src/events/documentId.ts +0 -56
- package/src/events/events.ts +0 -47
- package/src/events/identification.ts +0 -311
- package/src/events/index.ts +0 -15
- package/src/events/processors/AnalyticsProcessor.ts +0 -64
- package/src/events/processors/AuditLogsProcessor.ts +0 -92
- package/src/events/processors/LoggingProcessor.ts +0 -36
- package/src/events/processors/Processors.ts +0 -52
- package/src/events/processors/async/DocumentUpdateProcessor.ts +0 -38
- package/src/events/processors/index.ts +0 -19
- package/src/events/processors/posthog/PosthogProcessor.ts +0 -118
- package/src/events/processors/posthog/index.ts +0 -3
- package/src/events/processors/posthog/rateLimiting.ts +0 -106
- package/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +0 -164
- package/src/events/processors/types.ts +0 -1
- package/src/events/publishers/account.ts +0 -41
- package/src/events/publishers/ai.ts +0 -21
- package/src/events/publishers/app.ts +0 -168
- package/src/events/publishers/auditLog.ts +0 -26
- package/src/events/publishers/auth.ts +0 -73
- package/src/events/publishers/automation.ts +0 -110
- package/src/events/publishers/backfill.ts +0 -74
- package/src/events/publishers/backup.ts +0 -42
- package/src/events/publishers/datasource.ts +0 -48
- package/src/events/publishers/email.ts +0 -17
- package/src/events/publishers/environmentVariable.ts +0 -38
- package/src/events/publishers/group.ts +0 -99
- package/src/events/publishers/index.ts +0 -25
- package/src/events/publishers/installation.ts +0 -38
- package/src/events/publishers/layout.ts +0 -26
- package/src/events/publishers/license.ts +0 -84
- package/src/events/publishers/org.ts +0 -37
- package/src/events/publishers/plugin.ts +0 -47
- package/src/events/publishers/query.ts +0 -89
- package/src/events/publishers/role.ts +0 -62
- package/src/events/publishers/rows.ts +0 -29
- package/src/events/publishers/screen.ts +0 -36
- package/src/events/publishers/serve.ts +0 -43
- package/src/events/publishers/table.ts +0 -70
- package/src/events/publishers/user.ts +0 -202
- package/src/events/publishers/view.ts +0 -107
- package/src/features/features.ts +0 -277
- package/src/features/index.ts +0 -2
- package/src/features/tests/features.spec.ts +0 -267
- package/src/features/tests/utils.ts +0 -64
- package/src/helpers.ts +0 -9
- package/src/index.ts +0 -59
- package/src/installation.ts +0 -115
- package/src/logging/alerts.ts +0 -26
- package/src/logging/correlation/correlation.ts +0 -15
- package/src/logging/correlation/index.ts +0 -1
- package/src/logging/correlation/middleware.ts +0 -18
- package/src/logging/index.ts +0 -4
- package/src/logging/pino/logger.ts +0 -239
- package/src/logging/pino/middleware.ts +0 -48
- package/src/logging/system.ts +0 -81
- package/src/logging/tests/system.spec.ts +0 -61
- package/src/middleware/adminOnly.ts +0 -9
- package/src/middleware/auditLog.ts +0 -6
- package/src/middleware/authenticated.ts +0 -247
- package/src/middleware/builderOnly.ts +0 -21
- package/src/middleware/builderOrAdmin.ts +0 -21
- package/src/middleware/contentSecurityPolicy.ts +0 -113
- package/src/middleware/csrf.ts +0 -81
- package/src/middleware/errorHandling.ts +0 -43
- package/src/middleware/index.ts +0 -24
- package/src/middleware/internalApi.ts +0 -23
- package/src/middleware/ip.ts +0 -12
- package/src/middleware/joi-validator.ts +0 -58
- package/src/middleware/matchers.ts +0 -39
- package/src/middleware/passport/datasource/google.ts +0 -102
- package/src/middleware/passport/local.ts +0 -54
- package/src/middleware/passport/sso/google.ts +0 -77
- package/src/middleware/passport/sso/oidc.ts +0 -152
- package/src/middleware/passport/sso/sso.ts +0 -138
- package/src/middleware/passport/sso/tests/google.spec.ts +0 -68
- package/src/middleware/passport/sso/tests/oidc.spec.ts +0 -144
- package/src/middleware/passport/sso/tests/sso.spec.ts +0 -197
- package/src/middleware/passport/utils.ts +0 -38
- package/src/middleware/querystringToBody.ts +0 -28
- package/src/middleware/tenancy.ts +0 -36
- package/src/middleware/tests/builder.spec.ts +0 -181
- package/src/middleware/tests/contentSecurityPolicy.spec.ts +0 -75
- package/src/middleware/tests/matchers.spec.ts +0 -100
- package/src/migrations/definitions.ts +0 -40
- package/src/migrations/index.ts +0 -2
- package/src/migrations/migrations.ts +0 -186
- package/src/migrations/tests/__snapshots__/migrations.spec.ts.snap +0 -11
- package/src/migrations/tests/migrations.spec.ts +0 -64
- package/src/objectStore/buckets/app.ts +0 -53
- package/src/objectStore/buckets/global.ts +0 -29
- package/src/objectStore/buckets/index.ts +0 -3
- package/src/objectStore/buckets/plugins.ts +0 -71
- package/src/objectStore/buckets/tests/app.spec.ts +0 -161
- package/src/objectStore/buckets/tests/global.spec.ts +0 -74
- package/src/objectStore/buckets/tests/plugins.spec.ts +0 -111
- package/src/objectStore/cloudfront.ts +0 -41
- package/src/objectStore/index.ts +0 -3
- package/src/objectStore/objectStore.ts +0 -585
- package/src/objectStore/utils.ts +0 -113
- package/src/platform/index.ts +0 -3
- package/src/platform/platformDb.ts +0 -6
- package/src/platform/tenants.ts +0 -101
- package/src/platform/tests/tenants.spec.ts +0 -26
- package/src/platform/users.ts +0 -129
- package/src/plugin/index.ts +0 -1
- package/src/plugin/tests/validation.spec.ts +0 -209
- package/src/plugin/utils.ts +0 -175
- package/src/queue/constants.ts +0 -8
- package/src/queue/inMemoryQueue.ts +0 -189
- package/src/queue/index.ts +0 -2
- package/src/queue/listeners.ts +0 -199
- package/src/queue/queue.ts +0 -84
- package/src/redis/index.ts +0 -6
- package/src/redis/init.ts +0 -118
- package/src/redis/redis.ts +0 -358
- package/src/redis/redlockImpl.ts +0 -155
- package/src/redis/tests/redis.spec.ts +0 -207
- package/src/redis/tests/redlockImpl.spec.ts +0 -105
- package/src/redis/utils.ts +0 -128
- package/src/security/auth.ts +0 -24
- package/src/security/encryption.ts +0 -185
- package/src/security/index.ts +0 -1
- package/src/security/permissions.ts +0 -166
- package/src/security/roles.ts +0 -655
- package/src/security/secrets.ts +0 -20
- package/src/security/sessions.ts +0 -123
- package/src/security/tests/auth.spec.ts +0 -45
- package/src/security/tests/encryption.spec.ts +0 -31
- package/src/security/tests/permissions.spec.ts +0 -146
- package/src/security/tests/secrets.spec.ts +0 -35
- package/src/security/tests/sessions.spec.ts +0 -12
- package/src/sql/designDoc.ts +0 -17
- package/src/sql/index.ts +0 -5
- package/src/sql/sql.ts +0 -1854
- package/src/sql/sqlTable.ts +0 -319
- package/src/sql/utils.ts +0 -193
- package/src/tenancy/db.ts +0 -6
- package/src/tenancy/index.ts +0 -2
- package/src/tenancy/tenancy.ts +0 -148
- package/src/tenancy/tests/tenancy.spec.ts +0 -184
- package/src/timers/index.ts +0 -1
- package/src/timers/timers.ts +0 -22
- package/src/users/db.ts +0 -582
- package/src/users/events.ts +0 -176
- package/src/users/index.ts +0 -4
- package/src/users/lookup.ts +0 -99
- package/src/users/test/db.spec.ts +0 -188
- package/src/users/test/utils.spec.ts +0 -67
- package/src/users/users.ts +0 -353
- package/src/users/utils.ts +0 -81
- package/src/utils/Duration.ts +0 -56
- package/src/utils/hashing.ts +0 -15
- package/src/utils/index.ts +0 -4
- package/src/utils/stringUtils.ts +0 -8
- package/src/utils/tests/Duration.spec.ts +0 -19
- package/src/utils/tests/utils.spec.ts +0 -204
- package/src/utils/utils.ts +0 -249
- package/tests/core/logging.ts +0 -34
- package/tests/core/users/users.spec.js +0 -53
- package/tests/core/utilities/index.ts +0 -7
- package/tests/core/utilities/jestUtils.ts +0 -33
- package/tests/core/utilities/mocks/alerts.ts +0 -4
- package/tests/core/utilities/mocks/date.ts +0 -3
- package/tests/core/utilities/mocks/events.ts +0 -132
- package/tests/core/utilities/mocks/index.ts +0 -9
- package/tests/core/utilities/mocks/licenses.ts +0 -119
- package/tests/core/utilities/queue.ts +0 -9
- package/tests/core/utilities/structures/Chance.ts +0 -20
- package/tests/core/utilities/structures/accounts.ts +0 -80
- package/tests/core/utilities/structures/apps.ts +0 -21
- package/tests/core/utilities/structures/common.ts +0 -7
- package/tests/core/utilities/structures/db.ts +0 -12
- package/tests/core/utilities/structures/documents/index.ts +0 -1
- package/tests/core/utilities/structures/documents/platform/index.ts +0 -1
- package/tests/core/utilities/structures/documents/platform/installation.ts +0 -12
- package/tests/core/utilities/structures/generator.ts +0 -3
- package/tests/core/utilities/structures/index.ts +0 -15
- package/tests/core/utilities/structures/koa.ts +0 -16
- package/tests/core/utilities/structures/licenses.ts +0 -190
- package/tests/core/utilities/structures/plugins.ts +0 -19
- package/tests/core/utilities/structures/quotas.ts +0 -72
- package/tests/core/utilities/structures/scim.ts +0 -80
- package/tests/core/utilities/structures/sso.ts +0 -118
- package/tests/core/utilities/structures/tenants.ts +0 -5
- package/tests/core/utilities/structures/userGroups.ts +0 -10
- package/tests/core/utilities/structures/users.ts +0 -89
- package/tests/core/utilities/testContainerUtils.ts +0 -165
- package/tests/core/utilities/utils/index.ts +0 -2
- package/tests/core/utilities/utils/queue.ts +0 -27
- package/tests/core/utilities/utils/time.ts +0 -3
- package/tests/extra/DBTestConfiguration.ts +0 -36
- package/tests/extra/index.ts +0 -2
- package/tests/extra/testEnv.ts +0 -95
- package/tests/index.ts +0 -2
- package/tests/jestEnv.ts +0 -10
- package/tests/jestSetup.ts +0 -36
|
@@ -1,585 +0,0 @@
|
|
|
1
|
-
const sanitize = require("sanitize-s3-objectkey")
|
|
2
|
-
|
|
3
|
-
import AWS from "aws-sdk"
|
|
4
|
-
import stream, { Readable } from "stream"
|
|
5
|
-
import fetch from "node-fetch"
|
|
6
|
-
import tar from "tar-fs"
|
|
7
|
-
import zlib from "zlib"
|
|
8
|
-
import { promisify } from "util"
|
|
9
|
-
import { join } from "path"
|
|
10
|
-
import fs, { PathLike, ReadStream } from "fs"
|
|
11
|
-
import env from "../environment"
|
|
12
|
-
import { bucketTTLConfig, budibaseTempDir } from "./utils"
|
|
13
|
-
import { v4 } from "uuid"
|
|
14
|
-
import { APP_PREFIX, APP_DEV_PREFIX } from "../db"
|
|
15
|
-
import fsp from "fs/promises"
|
|
16
|
-
import { HeadObjectOutput } from "aws-sdk/clients/s3"
|
|
17
|
-
import { ReadableStream } from "stream/web"
|
|
18
|
-
|
|
19
|
-
const streamPipeline = promisify(stream.pipeline)
|
|
20
|
-
// use this as a temporary store of buckets that are being created
|
|
21
|
-
const STATE = {
|
|
22
|
-
bucketCreationPromises: {},
|
|
23
|
-
}
|
|
24
|
-
export const SIGNED_FILE_PREFIX = "/files/signed"
|
|
25
|
-
|
|
26
|
-
type ListParams = {
|
|
27
|
-
ContinuationToken?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type BaseUploadParams = {
|
|
31
|
-
bucket: string
|
|
32
|
-
filename: string
|
|
33
|
-
type?: string | null
|
|
34
|
-
metadata?: { [key: string]: string | undefined }
|
|
35
|
-
body?: ReadableStream | Buffer
|
|
36
|
-
ttl?: number
|
|
37
|
-
addTTL?: boolean
|
|
38
|
-
extra?: any
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type UploadParams = BaseUploadParams & {
|
|
42
|
-
path?: string | PathLike
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type StreamTypes = ReadStream | NodeJS.ReadableStream
|
|
46
|
-
|
|
47
|
-
export type StreamUploadParams = BaseUploadParams & {
|
|
48
|
-
stream?: StreamTypes
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const CONTENT_TYPE_MAP: any = {
|
|
52
|
-
txt: "text/plain",
|
|
53
|
-
html: "text/html",
|
|
54
|
-
css: "text/css",
|
|
55
|
-
js: "application/javascript",
|
|
56
|
-
json: "application/json",
|
|
57
|
-
gz: "application/gzip",
|
|
58
|
-
svg: "image/svg+xml",
|
|
59
|
-
form: "multipart/form-data",
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const STRING_CONTENT_TYPES = [
|
|
63
|
-
CONTENT_TYPE_MAP.html,
|
|
64
|
-
CONTENT_TYPE_MAP.css,
|
|
65
|
-
CONTENT_TYPE_MAP.js,
|
|
66
|
-
CONTENT_TYPE_MAP.json,
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
// does normal sanitization and then swaps dev apps to apps
|
|
70
|
-
export function sanitizeKey(input: string) {
|
|
71
|
-
return sanitize(sanitizeBucket(input)).replace(/\\/g, "/")
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// simply handles the dev app to app conversion
|
|
75
|
-
export function sanitizeBucket(input: string) {
|
|
76
|
-
return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Gets a connection to the object store using the S3 SDK.
|
|
81
|
-
* @param bucket the name of the bucket which blobs will be uploaded/retrieved from.
|
|
82
|
-
* @param opts configuration for the object store.
|
|
83
|
-
* @return an S3 object store object, check S3 Nodejs SDK for usage.
|
|
84
|
-
* @constructor
|
|
85
|
-
*/
|
|
86
|
-
export function ObjectStore(
|
|
87
|
-
bucket: string,
|
|
88
|
-
opts: { presigning: boolean } = { presigning: false }
|
|
89
|
-
) {
|
|
90
|
-
const config: AWS.S3.ClientConfiguration = {
|
|
91
|
-
s3ForcePathStyle: true,
|
|
92
|
-
signatureVersion: "v4",
|
|
93
|
-
apiVersion: "2006-03-01",
|
|
94
|
-
accessKeyId: env.MINIO_ACCESS_KEY,
|
|
95
|
-
secretAccessKey: env.MINIO_SECRET_KEY,
|
|
96
|
-
region: env.AWS_REGION,
|
|
97
|
-
}
|
|
98
|
-
if (bucket) {
|
|
99
|
-
config.params = {
|
|
100
|
-
Bucket: sanitizeBucket(bucket),
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// for AWS Credentials using temporary session token
|
|
105
|
-
if (!env.MINIO_ENABLED && env.AWS_SESSION_TOKEN) {
|
|
106
|
-
config.sessionToken = env.AWS_SESSION_TOKEN
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// custom S3 is in use i.e. minio
|
|
110
|
-
if (env.MINIO_URL) {
|
|
111
|
-
if (opts.presigning && env.MINIO_ENABLED) {
|
|
112
|
-
// IMPORTANT: Signed urls will inspect the host header of the request.
|
|
113
|
-
// Normally a signed url will need to be generated with a specified host in mind.
|
|
114
|
-
// To support dynamic hosts, e.g. some unknown self-hosted installation url,
|
|
115
|
-
// use a predefined host. The host 'minio-service' is also forwarded to minio requests via nginx
|
|
116
|
-
config.endpoint = "minio-service"
|
|
117
|
-
} else {
|
|
118
|
-
config.endpoint = env.MINIO_URL
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return new AWS.S3(config)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Given an object store and a bucket name this will make sure the bucket exists,
|
|
127
|
-
* if it does not exist then it will create it.
|
|
128
|
-
*/
|
|
129
|
-
export async function createBucketIfNotExists(
|
|
130
|
-
client: any,
|
|
131
|
-
bucketName: string
|
|
132
|
-
): Promise<{ created: boolean; exists: boolean }> {
|
|
133
|
-
bucketName = sanitizeBucket(bucketName)
|
|
134
|
-
try {
|
|
135
|
-
await client
|
|
136
|
-
.headBucket({
|
|
137
|
-
Bucket: bucketName,
|
|
138
|
-
})
|
|
139
|
-
.promise()
|
|
140
|
-
return { created: false, exists: true }
|
|
141
|
-
} catch (err: any) {
|
|
142
|
-
const promises: any = STATE.bucketCreationPromises
|
|
143
|
-
const doesntExist = err.statusCode === 404,
|
|
144
|
-
noAccess = err.statusCode === 403
|
|
145
|
-
if (promises[bucketName]) {
|
|
146
|
-
await promises[bucketName]
|
|
147
|
-
return { created: false, exists: true }
|
|
148
|
-
} else if (doesntExist || noAccess) {
|
|
149
|
-
if (doesntExist) {
|
|
150
|
-
promises[bucketName] = client
|
|
151
|
-
.createBucket({
|
|
152
|
-
Bucket: bucketName,
|
|
153
|
-
})
|
|
154
|
-
.promise()
|
|
155
|
-
await promises[bucketName]
|
|
156
|
-
delete promises[bucketName]
|
|
157
|
-
return { created: true, exists: false }
|
|
158
|
-
} else {
|
|
159
|
-
throw new Error("Access denied to object store bucket." + err)
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
throw new Error("Unable to write to object store bucket.")
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Uploads the contents of a file given the required parameters, useful when
|
|
168
|
-
* temp files in use (for example file uploaded as an attachment).
|
|
169
|
-
*/
|
|
170
|
-
export async function upload({
|
|
171
|
-
bucket: bucketName,
|
|
172
|
-
filename,
|
|
173
|
-
path,
|
|
174
|
-
type,
|
|
175
|
-
metadata,
|
|
176
|
-
body,
|
|
177
|
-
ttl,
|
|
178
|
-
}: UploadParams) {
|
|
179
|
-
const extension = filename.split(".").pop()
|
|
180
|
-
|
|
181
|
-
const fileBytes = path ? (await fsp.open(path)).createReadStream() : body
|
|
182
|
-
|
|
183
|
-
const objectStore = ObjectStore(bucketName)
|
|
184
|
-
const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
|
|
185
|
-
|
|
186
|
-
if (ttl && bucketCreated.created) {
|
|
187
|
-
let ttlConfig = bucketTTLConfig(bucketName, ttl)
|
|
188
|
-
await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
let contentType = type
|
|
192
|
-
if (!contentType) {
|
|
193
|
-
contentType = extension
|
|
194
|
-
? CONTENT_TYPE_MAP[extension.toLowerCase()]
|
|
195
|
-
: CONTENT_TYPE_MAP.txt
|
|
196
|
-
}
|
|
197
|
-
const config: any = {
|
|
198
|
-
// windows file paths need to be converted to forward slashes for s3
|
|
199
|
-
Key: sanitizeKey(filename),
|
|
200
|
-
Body: fileBytes,
|
|
201
|
-
ContentType: contentType,
|
|
202
|
-
}
|
|
203
|
-
if (metadata && typeof metadata === "object") {
|
|
204
|
-
// remove any nullish keys from the metadata object, as these may be considered invalid
|
|
205
|
-
for (let key of Object.keys(metadata)) {
|
|
206
|
-
if (!metadata[key] || typeof metadata[key] !== "string") {
|
|
207
|
-
delete metadata[key]
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
config.Metadata = metadata
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return objectStore.upload(config).promise()
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Similar to the upload function but can be used to send a file stream
|
|
218
|
-
* through to the object store.
|
|
219
|
-
*/
|
|
220
|
-
export async function streamUpload({
|
|
221
|
-
bucket: bucketName,
|
|
222
|
-
stream,
|
|
223
|
-
filename,
|
|
224
|
-
type,
|
|
225
|
-
extra,
|
|
226
|
-
ttl,
|
|
227
|
-
}: StreamUploadParams) {
|
|
228
|
-
if (!stream) {
|
|
229
|
-
throw new Error("Stream to upload is invalid/undefined")
|
|
230
|
-
}
|
|
231
|
-
const extension = filename.split(".").pop()
|
|
232
|
-
const objectStore = ObjectStore(bucketName)
|
|
233
|
-
const bucketCreated = await createBucketIfNotExists(objectStore, bucketName)
|
|
234
|
-
|
|
235
|
-
if (ttl && bucketCreated.created) {
|
|
236
|
-
let ttlConfig = bucketTTLConfig(bucketName, ttl)
|
|
237
|
-
await objectStore.putBucketLifecycleConfiguration(ttlConfig).promise()
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Set content type for certain known extensions
|
|
241
|
-
if (filename?.endsWith(".js")) {
|
|
242
|
-
extra = {
|
|
243
|
-
...extra,
|
|
244
|
-
ContentType: "application/javascript",
|
|
245
|
-
}
|
|
246
|
-
} else if (filename?.endsWith(".svg")) {
|
|
247
|
-
extra = {
|
|
248
|
-
...extra,
|
|
249
|
-
ContentType: "image",
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
let contentType = type
|
|
254
|
-
if (!contentType) {
|
|
255
|
-
contentType = extension
|
|
256
|
-
? CONTENT_TYPE_MAP[extension.toLowerCase()]
|
|
257
|
-
: CONTENT_TYPE_MAP.txt
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const bucket = sanitizeBucket(bucketName),
|
|
261
|
-
objKey = sanitizeKey(filename)
|
|
262
|
-
const params = {
|
|
263
|
-
Bucket: bucket,
|
|
264
|
-
Key: objKey,
|
|
265
|
-
Body: stream,
|
|
266
|
-
ContentType: contentType,
|
|
267
|
-
...extra,
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const details = await objectStore.upload(params).promise()
|
|
271
|
-
const headDetails = await objectStore
|
|
272
|
-
.headObject({
|
|
273
|
-
Bucket: bucket,
|
|
274
|
-
Key: objKey,
|
|
275
|
-
})
|
|
276
|
-
.promise()
|
|
277
|
-
return {
|
|
278
|
-
...details,
|
|
279
|
-
ContentLength: headDetails.ContentLength,
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* retrieves the contents of a file from the object store, if it is a known content type it
|
|
285
|
-
* will be converted, otherwise it will be returned as a buffer stream.
|
|
286
|
-
*/
|
|
287
|
-
export async function retrieve(bucketName: string, filepath: string) {
|
|
288
|
-
const objectStore = ObjectStore(bucketName)
|
|
289
|
-
const params = {
|
|
290
|
-
Bucket: sanitizeBucket(bucketName),
|
|
291
|
-
Key: sanitizeKey(filepath),
|
|
292
|
-
}
|
|
293
|
-
const response: any = await objectStore.getObject(params).promise()
|
|
294
|
-
// currently these are all strings
|
|
295
|
-
if (STRING_CONTENT_TYPES.includes(response.ContentType)) {
|
|
296
|
-
return response.Body.toString("utf8")
|
|
297
|
-
} else {
|
|
298
|
-
return response.Body
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export async function listAllObjects(bucketName: string, path: string) {
|
|
303
|
-
const objectStore = ObjectStore(bucketName)
|
|
304
|
-
const list = (params: ListParams = {}) => {
|
|
305
|
-
return objectStore
|
|
306
|
-
.listObjectsV2({
|
|
307
|
-
...params,
|
|
308
|
-
Bucket: sanitizeBucket(bucketName),
|
|
309
|
-
Prefix: sanitizeKey(path),
|
|
310
|
-
})
|
|
311
|
-
.promise()
|
|
312
|
-
}
|
|
313
|
-
let isTruncated = false,
|
|
314
|
-
token,
|
|
315
|
-
objects: AWS.S3.Types.Object[] = []
|
|
316
|
-
do {
|
|
317
|
-
let params: ListParams = {}
|
|
318
|
-
if (token) {
|
|
319
|
-
params.ContinuationToken = token
|
|
320
|
-
}
|
|
321
|
-
const response = await list(params)
|
|
322
|
-
if (response.Contents) {
|
|
323
|
-
objects = objects.concat(response.Contents)
|
|
324
|
-
}
|
|
325
|
-
isTruncated = !!response.IsTruncated
|
|
326
|
-
token = response.NextContinuationToken
|
|
327
|
-
} while (isTruncated && token)
|
|
328
|
-
return objects
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Generate a presigned url with a default TTL of 1 hour
|
|
333
|
-
*/
|
|
334
|
-
export function getPresignedUrl(
|
|
335
|
-
bucketName: string,
|
|
336
|
-
key: string,
|
|
337
|
-
durationSeconds: number = 3600
|
|
338
|
-
) {
|
|
339
|
-
const objectStore = ObjectStore(bucketName, { presigning: true })
|
|
340
|
-
const params = {
|
|
341
|
-
Bucket: sanitizeBucket(bucketName),
|
|
342
|
-
Key: sanitizeKey(key),
|
|
343
|
-
Expires: durationSeconds,
|
|
344
|
-
}
|
|
345
|
-
const url = objectStore.getSignedUrl("getObject", params)
|
|
346
|
-
|
|
347
|
-
if (!env.MINIO_ENABLED) {
|
|
348
|
-
// return the full URL to the client
|
|
349
|
-
return url
|
|
350
|
-
} else {
|
|
351
|
-
// return the path only to the client
|
|
352
|
-
// use the presigned url route to ensure the static
|
|
353
|
-
// hostname will be used in the request
|
|
354
|
-
const signedUrl = new URL(url)
|
|
355
|
-
const path = signedUrl.pathname
|
|
356
|
-
const query = signedUrl.search
|
|
357
|
-
return `${SIGNED_FILE_PREFIX}${path}${query}`
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Same as retrieval function but puts to a temporary file.
|
|
363
|
-
*/
|
|
364
|
-
export async function retrieveToTmp(bucketName: string, filepath: string) {
|
|
365
|
-
bucketName = sanitizeBucket(bucketName)
|
|
366
|
-
filepath = sanitizeKey(filepath)
|
|
367
|
-
const data = await retrieve(bucketName, filepath)
|
|
368
|
-
const outputPath = join(budibaseTempDir(), v4())
|
|
369
|
-
fs.writeFileSync(outputPath, data)
|
|
370
|
-
return outputPath
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export async function retrieveDirectory(bucketName: string, path: string) {
|
|
374
|
-
let writePath = join(budibaseTempDir(), v4())
|
|
375
|
-
fs.mkdirSync(writePath)
|
|
376
|
-
const objects = await listAllObjects(bucketName, path)
|
|
377
|
-
let streams = await Promise.all(
|
|
378
|
-
objects.map(obj => getReadStream(bucketName, obj.Key!))
|
|
379
|
-
)
|
|
380
|
-
let count = 0
|
|
381
|
-
const writePromises: Promise<Error>[] = []
|
|
382
|
-
for (let obj of objects) {
|
|
383
|
-
const filename = obj.Key!
|
|
384
|
-
const stream = streams[count++]
|
|
385
|
-
const possiblePath = filename.split("/")
|
|
386
|
-
const dirs = possiblePath.slice(0, possiblePath.length - 1)
|
|
387
|
-
const possibleDir = join(writePath, ...dirs)
|
|
388
|
-
if (possiblePath.length > 1 && !fs.existsSync(possibleDir)) {
|
|
389
|
-
fs.mkdirSync(possibleDir, { recursive: true })
|
|
390
|
-
}
|
|
391
|
-
const writeStream = fs.createWriteStream(join(writePath, ...possiblePath), {
|
|
392
|
-
mode: 0o644,
|
|
393
|
-
})
|
|
394
|
-
stream.pipe(writeStream)
|
|
395
|
-
writePromises.push(
|
|
396
|
-
new Promise((resolve, reject) => {
|
|
397
|
-
stream.on("finish", resolve)
|
|
398
|
-
stream.on("error", reject)
|
|
399
|
-
writeStream.on("error", reject)
|
|
400
|
-
})
|
|
401
|
-
)
|
|
402
|
-
}
|
|
403
|
-
await Promise.all(writePromises)
|
|
404
|
-
return writePath
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Delete a single file.
|
|
409
|
-
*/
|
|
410
|
-
export async function deleteFile(bucketName: string, filepath: string) {
|
|
411
|
-
const objectStore = ObjectStore(bucketName)
|
|
412
|
-
await createBucketIfNotExists(objectStore, bucketName)
|
|
413
|
-
const params = {
|
|
414
|
-
Bucket: bucketName,
|
|
415
|
-
Key: sanitizeKey(filepath),
|
|
416
|
-
}
|
|
417
|
-
return objectStore.deleteObject(params).promise()
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export async function deleteFiles(bucketName: string, filepaths: string[]) {
|
|
421
|
-
const objectStore = ObjectStore(bucketName)
|
|
422
|
-
await createBucketIfNotExists(objectStore, bucketName)
|
|
423
|
-
const params = {
|
|
424
|
-
Bucket: bucketName,
|
|
425
|
-
Delete: {
|
|
426
|
-
Objects: filepaths.map((path: any) => ({ Key: sanitizeKey(path) })),
|
|
427
|
-
},
|
|
428
|
-
}
|
|
429
|
-
return objectStore.deleteObjects(params).promise()
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Delete a path, including everything within.
|
|
434
|
-
*/
|
|
435
|
-
export async function deleteFolder(
|
|
436
|
-
bucketName: string,
|
|
437
|
-
folder: string
|
|
438
|
-
): Promise<any> {
|
|
439
|
-
bucketName = sanitizeBucket(bucketName)
|
|
440
|
-
folder = sanitizeKey(folder)
|
|
441
|
-
const client = ObjectStore(bucketName)
|
|
442
|
-
const listParams = {
|
|
443
|
-
Bucket: bucketName,
|
|
444
|
-
Prefix: folder,
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
const existingObjectsResponse = await client.listObjects(listParams).promise()
|
|
448
|
-
if (existingObjectsResponse.Contents?.length === 0) {
|
|
449
|
-
return
|
|
450
|
-
}
|
|
451
|
-
const deleteParams: any = {
|
|
452
|
-
Bucket: bucketName,
|
|
453
|
-
Delete: {
|
|
454
|
-
Objects: [],
|
|
455
|
-
},
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
existingObjectsResponse.Contents?.forEach((content: any) => {
|
|
459
|
-
deleteParams.Delete.Objects.push({ Key: content.Key })
|
|
460
|
-
})
|
|
461
|
-
|
|
462
|
-
const deleteResponse = await client.deleteObjects(deleteParams).promise()
|
|
463
|
-
// can only empty 1000 items at once
|
|
464
|
-
if (deleteResponse.Deleted?.length === 1000) {
|
|
465
|
-
return deleteFolder(bucketName, folder)
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export async function uploadDirectory(
|
|
470
|
-
bucketName: string,
|
|
471
|
-
localPath: string,
|
|
472
|
-
bucketPath: string
|
|
473
|
-
) {
|
|
474
|
-
bucketName = sanitizeBucket(bucketName)
|
|
475
|
-
let uploads = []
|
|
476
|
-
const files = fs.readdirSync(localPath, { withFileTypes: true })
|
|
477
|
-
for (let file of files) {
|
|
478
|
-
const path = sanitizeKey(join(bucketPath, file.name))
|
|
479
|
-
const local = join(localPath, file.name)
|
|
480
|
-
if (file.isDirectory()) {
|
|
481
|
-
uploads.push(uploadDirectory(bucketName, local, path))
|
|
482
|
-
} else {
|
|
483
|
-
uploads.push(
|
|
484
|
-
streamUpload({
|
|
485
|
-
bucket: bucketName,
|
|
486
|
-
filename: path,
|
|
487
|
-
stream: fs.createReadStream(local),
|
|
488
|
-
})
|
|
489
|
-
)
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
await Promise.all(uploads)
|
|
493
|
-
return files
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export async function downloadTarballDirect(
|
|
497
|
-
url: string,
|
|
498
|
-
path: string,
|
|
499
|
-
headers = {}
|
|
500
|
-
) {
|
|
501
|
-
path = sanitizeKey(path)
|
|
502
|
-
const response = await fetch(url, { headers })
|
|
503
|
-
if (!response.ok) {
|
|
504
|
-
throw new Error(`unexpected response ${response.statusText}`)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
await streamPipeline(response.body, zlib.createUnzip(), tar.extract(path))
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
export async function downloadTarball(
|
|
511
|
-
url: string,
|
|
512
|
-
bucketName: string,
|
|
513
|
-
path: string
|
|
514
|
-
) {
|
|
515
|
-
bucketName = sanitizeBucket(bucketName)
|
|
516
|
-
path = sanitizeKey(path)
|
|
517
|
-
const response = await fetch(url)
|
|
518
|
-
if (!response.ok) {
|
|
519
|
-
throw new Error(`unexpected response ${response.statusText}`)
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const tmpPath = join(budibaseTempDir(), path)
|
|
523
|
-
await streamPipeline(response.body, zlib.createUnzip(), tar.extract(tmpPath))
|
|
524
|
-
if (!env.isTest() && env.SELF_HOSTED) {
|
|
525
|
-
await uploadDirectory(bucketName, tmpPath, path)
|
|
526
|
-
}
|
|
527
|
-
// return the temporary path incase there is a use for it
|
|
528
|
-
return tmpPath
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
export async function getReadStream(
|
|
532
|
-
bucketName: string,
|
|
533
|
-
path: string
|
|
534
|
-
): Promise<Readable> {
|
|
535
|
-
bucketName = sanitizeBucket(bucketName)
|
|
536
|
-
path = sanitizeKey(path)
|
|
537
|
-
const client = ObjectStore(bucketName)
|
|
538
|
-
const params = {
|
|
539
|
-
Bucket: bucketName,
|
|
540
|
-
Key: path,
|
|
541
|
-
}
|
|
542
|
-
return client.getObject(params).createReadStream()
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
export async function getObjectMetadata(
|
|
546
|
-
bucket: string,
|
|
547
|
-
path: string
|
|
548
|
-
): Promise<HeadObjectOutput> {
|
|
549
|
-
bucket = sanitizeBucket(bucket)
|
|
550
|
-
path = sanitizeKey(path)
|
|
551
|
-
|
|
552
|
-
const client = ObjectStore(bucket)
|
|
553
|
-
const params = {
|
|
554
|
-
Bucket: bucket,
|
|
555
|
-
Key: path,
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
try {
|
|
559
|
-
return await client.headObject(params).promise()
|
|
560
|
-
} catch (err: any) {
|
|
561
|
-
throw new Error("Unable to retrieve metadata from object")
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/*
|
|
566
|
-
Given a signed url like '/files/signed/tmp-files-attachments/app_123456/myfile.txt' extract
|
|
567
|
-
the bucket and the path from it
|
|
568
|
-
*/
|
|
569
|
-
export function extractBucketAndPath(
|
|
570
|
-
url: string
|
|
571
|
-
): { bucket: string; path: string } | null {
|
|
572
|
-
const baseUrl = url.split("?")[0]
|
|
573
|
-
|
|
574
|
-
const regex = new RegExp(
|
|
575
|
-
`^${SIGNED_FILE_PREFIX}/(?<bucket>[^/]+)/(?<path>.+)$`
|
|
576
|
-
)
|
|
577
|
-
const match = baseUrl.match(regex)
|
|
578
|
-
|
|
579
|
-
if (match && match.groups) {
|
|
580
|
-
const { bucket, path } = match.groups
|
|
581
|
-
return { bucket, path }
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
return null
|
|
585
|
-
}
|
package/src/objectStore/utils.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import path, { join } from "path"
|
|
2
|
-
import { tmpdir } from "os"
|
|
3
|
-
import fs from "fs"
|
|
4
|
-
import env from "../environment"
|
|
5
|
-
import { PutBucketLifecycleConfigurationRequest } from "aws-sdk/clients/s3"
|
|
6
|
-
import * as objectStore from "./objectStore"
|
|
7
|
-
import {
|
|
8
|
-
AutomationAttachment,
|
|
9
|
-
AutomationAttachmentContent,
|
|
10
|
-
BucketedContent,
|
|
11
|
-
} from "@budibase/types"
|
|
12
|
-
import stream from "stream"
|
|
13
|
-
import streamWeb from "node:stream/web"
|
|
14
|
-
|
|
15
|
-
/****************************************************
|
|
16
|
-
* NOTE: When adding a new bucket - name *
|
|
17
|
-
* sure that S3 usages (like budibase-infra) *
|
|
18
|
-
* have been updated to have a unique bucket name. *
|
|
19
|
-
****************************************************/
|
|
20
|
-
// can't be an enum - only numbers can be used for computed types
|
|
21
|
-
export const ObjectStoreBuckets = {
|
|
22
|
-
BACKUPS: env.BACKUPS_BUCKET_NAME,
|
|
23
|
-
APPS: env.APPS_BUCKET_NAME,
|
|
24
|
-
TEMPLATES: env.TEMPLATES_BUCKET_NAME,
|
|
25
|
-
GLOBAL: env.GLOBAL_BUCKET_NAME,
|
|
26
|
-
PLUGINS: env.PLUGIN_BUCKET_NAME,
|
|
27
|
-
TEMP: env.TEMP_BUCKET_NAME,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const bbTmp = join(tmpdir(), ".budibase")
|
|
31
|
-
try {
|
|
32
|
-
fs.mkdirSync(bbTmp)
|
|
33
|
-
} catch (e: any) {
|
|
34
|
-
if (e.code !== "EEXIST") {
|
|
35
|
-
throw e
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function budibaseTempDir() {
|
|
40
|
-
return bbTmp
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const bucketTTLConfig = (
|
|
44
|
-
bucketName: string,
|
|
45
|
-
days: number
|
|
46
|
-
): PutBucketLifecycleConfigurationRequest => {
|
|
47
|
-
const lifecycleRule = {
|
|
48
|
-
ID: `${bucketName}-ExpireAfter${days}days`,
|
|
49
|
-
Prefix: "",
|
|
50
|
-
Status: "Enabled",
|
|
51
|
-
Expiration: {
|
|
52
|
-
Days: days,
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
const lifecycleConfiguration = {
|
|
56
|
-
Rules: [lifecycleRule],
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
Bucket: bucketName,
|
|
61
|
-
LifecycleConfiguration: lifecycleConfiguration,
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function processUrlAttachment(
|
|
66
|
-
attachment: AutomationAttachment
|
|
67
|
-
): Promise<AutomationAttachmentContent> {
|
|
68
|
-
const response = await fetch(attachment.url)
|
|
69
|
-
if (!response.ok || !response.body) {
|
|
70
|
-
throw new Error(`Unexpected response ${response.statusText}`)
|
|
71
|
-
}
|
|
72
|
-
const fallbackFilename = path.basename(new URL(attachment.url).pathname)
|
|
73
|
-
if (!response.body) {
|
|
74
|
-
throw new Error("No response received for attachment")
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
filename: attachment.filename || fallbackFilename,
|
|
78
|
-
content: stream.Readable.fromWeb(response.body as streamWeb.ReadableStream),
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function processObjectStoreAttachment(
|
|
83
|
-
attachment: AutomationAttachment
|
|
84
|
-
): Promise<BucketedContent> {
|
|
85
|
-
const result = objectStore.extractBucketAndPath(attachment.url)
|
|
86
|
-
|
|
87
|
-
if (result === null) {
|
|
88
|
-
throw new Error("Invalid signed URL")
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const { bucket, path: objectPath } = result
|
|
92
|
-
const readStream = await objectStore.getReadStream(bucket, objectPath)
|
|
93
|
-
const fallbackFilename = path.basename(objectPath)
|
|
94
|
-
return {
|
|
95
|
-
bucket,
|
|
96
|
-
path: objectPath,
|
|
97
|
-
filename: attachment.filename || fallbackFilename,
|
|
98
|
-
content: readStream,
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export async function processAutomationAttachment(
|
|
103
|
-
attachment: AutomationAttachment
|
|
104
|
-
): Promise<AutomationAttachmentContent | BucketedContent> {
|
|
105
|
-
const isFullyFormedUrl =
|
|
106
|
-
attachment.url?.startsWith("http://") ||
|
|
107
|
-
attachment.url?.startsWith("https://")
|
|
108
|
-
if (isFullyFormedUrl) {
|
|
109
|
-
return await processUrlAttachment(attachment)
|
|
110
|
-
} else {
|
|
111
|
-
return await processObjectStoreAttachment(attachment)
|
|
112
|
-
}
|
|
113
|
-
}
|
package/src/platform/index.ts
DELETED