Marc Roig c3f078328f
chore: backmerge develop into v5 main (#20678)
* Fix typo of query key from 'providers' to 'get-providers'

* Closing #19644 fix filters type

* fix patreon oauth 400 error

from lack of user-agent header

* test: backport cli tests (#20433)

* fix(chore): increase specificity of the Lightning icon color (#20467)

* test: temporarily disable broken edit ctb tests on CI (#20481)

* feat(cli): cloud cli commands (v4) (#20119)

* feat(cli): add cloud commands

Co-authored-by: Gonzalo Garcia <nouvellegon@gmail.com>
Co-authored-by: nathan-pichon <nathan.pichon@strapi.io>
Co-authored-by: Abdallah M <55534657+abdallahmz@users.noreply.github.com>

* v4.25.0 (#20500)

* NPS: Update frequency of the NPS (#20492)

* enhancement(admin): change postFirstDismissal and display delays

* enhancement(admin): change the display delay

* fix: prevent use of local ips on webhooks (#20487)

* chore: mask error on webhook manual trigger

* feat: prevent using local ips

* feat: display webhook edit error

* chore: pr suggestion

* chore: api tests

* chore: allow local ips on development

* chore: only run check on production

* feat: include internationalized urls

* fix: prettier

* Add: Strapi deploy command to README files

* v4.25.1

* Update @aws-sdk/client-s3 package

* feat(cli): add browser logout step (#20502)

* feat(cli): add browser log out step

* handle error whiile fetching the config

* Update LICENSE packing for packages for v4 (#20576)

* feat(create-strapi-app): remove the cloud project creation part (#20561)

* feat(create-strapi-app): remove the cloud project creation part

* fix(create-strapi-app): adding new parameter to cloud service instantiation

* Update Yarn to 4.3.1

Signed-off-by: Sora Morimoto <sora@morimoto.io>

* feat(cli): trigger login sequence when token is missing or invalid  (#20572)

* feat(cli): launch login when auth fails

* fix(cli): abstract create project fn

* fix(cli): guidelines

* fix(cli): rebase

---------

Co-authored-by: Gonzalo Andres Garcia <nouvellegon@gmail.com>

* fix clone entity, #20509 (#20531)

* chore: bump glob from 7.2.0 to 9.0.0

* chore: bump glob to v10.4.2 in core/strapi

* update yarn.lock

* removed redundant packages/core/strapi/src/load/glob.ts file

* v4.25.2 (#20675)

* fix: support string array

* v4.25.2

---------

Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com>

* Merge branch 'develop' into v5/main

* fix: ts errors

* fix: typescript issues

* fix: remove unused import

* Update packages/cli/create-strapi-app/src/create-strapi-app.ts

Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io>

* Update packages/cli/create-strapi-app/src/create-strapi-app.ts

Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io>

* Update packages/generators/app/src/resources/files/js/README.md

Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io>

* Update packages/generators/app/src/resources/files/ts/README.md

Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io>

* feat(cloud-cli): enable cloud-cli

---------

Signed-off-by: Sora Morimoto <sora@morimoto.io>
Co-authored-by: smoothdvd <madfxgao@gmail.com>
Co-authored-by: Micah Riggan <micahriggan@gmail.com>
Co-authored-by: Tewson Seeoun <tewson.seeoun@gmail.com>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
Co-authored-by: Simone <startae14@gmail.com>
Co-authored-by: Nathan Pichon <nathan.pichon@strapi.io>
Co-authored-by: Gonzalo Garcia <nouvellegon@gmail.com>
Co-authored-by: Abdallah M <55534657+abdallahmz@users.noreply.github.com>
Co-authored-by: Maxime Castres <mcastres@student.42.fr>
Co-authored-by: Convly <jean-sebastien.herbaux@epitech.eu>
Co-authored-by: Alex Supkay <asupkay1124@gmail.com>
Co-authored-by: Alexandre BODIN <alexandrebodin@users.noreply.github.com>
Co-authored-by: Sora Morimoto <sora@morimoto.io>
Co-authored-by: Alexandre Bodin <bodin.alex@gmail.com>
Co-authored-by: Kirill Verevkin <kira795@yandex.ru>
Co-authored-by: chrismuiruriz <chrismuiruri007@gmail.com>
2024-07-05 07:08:09 +02:00

172 lines
5.0 KiB
TypeScript

import type { ReadStream } from 'node:fs';
import { getOr } from 'lodash/fp';
import {
S3Client,
GetObjectCommand,
DeleteObjectCommand,
DeleteObjectCommandOutput,
PutObjectCommandInput,
CompleteMultipartUploadCommandOutput,
AbortMultipartUploadCommandOutput,
S3ClientConfig,
ObjectCannedACL,
} from '@aws-sdk/client-s3';
import type { AwsCredentialIdentity } from '@aws-sdk/types';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { Upload } from '@aws-sdk/lib-storage';
import { extractCredentials, isUrlFromBucket } from './utils';
export interface File {
name: string;
alternativeText?: string;
caption?: string;
width?: number;
height?: number;
formats?: Record<string, unknown>;
hash: string;
ext?: string;
mime: string;
size: number;
sizeInBytes: number;
url: string;
previewUrl?: string;
path?: string;
provider?: string;
provider_metadata?: Record<string, unknown>;
stream?: ReadStream;
buffer?: Buffer;
}
export type UploadCommandOutput = (
| CompleteMultipartUploadCommandOutput
| AbortMultipartUploadCommandOutput
) & {
Location: string;
};
export interface AWSParams {
Bucket: string; // making it required
ACL?: ObjectCannedACL;
signedUrlExpires?: number;
}
export interface DefaultOptions extends S3ClientConfig {
// TODO Remove this in V5
accessKeyId?: AwsCredentialIdentity['accessKeyId'];
secretAccessKey?: AwsCredentialIdentity['secretAccessKey'];
// Keep this for V5
credentials?: AwsCredentialIdentity;
params?: AWSParams;
[k: string]: any;
}
export type InitOptions = (DefaultOptions | { s3Options: DefaultOptions }) & {
baseUrl?: string;
rootPath?: string;
[k: string]: any;
};
const assertUrlProtocol = (url: string) => {
// Regex to test protocol like "http://", "https://"
return /^\w*:\/\//.test(url);
};
const getConfig = ({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) => {
if (Object.keys(legacyS3Options).length > 0) {
process.emitWarning(
"S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property."
);
}
const credentials = extractCredentials({ s3Options, ...legacyS3Options });
const config = {
...s3Options,
...legacyS3Options,
...(credentials ? { credentials } : {}),
};
config.params.ACL = getOr(ObjectCannedACL.public_read, ['params', 'ACL'], config);
return config;
};
export default {
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions) {
// TODO V5 change config structure to avoid having to do this
const config = getConfig({ baseUrl, rootPath, s3Options, ...legacyS3Options });
const s3Client = new S3Client(config);
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';
const getFileKey = (file: File) => {
const path = file.path ? `${file.path}/` : '';
return `${filePrefix}${path}${file.hash}${file.ext}`;
};
const upload = async (file: File, customParams: Partial<PutObjectCommandInput> = {}) => {
const fileKey = getFileKey(file);
const uploadObj = new Upload({
client: s3Client,
params: {
Bucket: config.params.Bucket,
Key: fileKey,
Body: file.stream || Buffer.from(file.buffer as any, 'binary'),
ACL: config.params.ACL,
ContentType: file.mime,
...customParams,
},
});
const upload = (await uploadObj.done()) as UploadCommandOutput;
if (assertUrlProtocol(upload.Location)) {
file.url = baseUrl ? `${baseUrl}/${fileKey}` : upload.Location;
} else {
// Default protocol to https protocol
file.url = `https://${upload.Location}`;
}
};
return {
isPrivate() {
return config.params.ACL === 'private';
},
async getSignedUrl(file: File, customParams: any): Promise<{ url: string }> {
// Do not sign the url if it does not come from the same bucket.
if (!isUrlFromBucket(file.url, config.params.Bucket, baseUrl)) {
return { url: file.url };
}
const fileKey = getFileKey(file);
const url = await getSignedUrl(
// @ts-expect-error - TODO fix client type
s3Client,
new GetObjectCommand({
Bucket: config.params.Bucket,
Key: fileKey,
...customParams,
}),
{
expiresIn: getOr(15 * 60, ['params', 'signedUrlExpires'], config),
}
);
return { url };
},
uploadStream(file: File, customParams = {}) {
return upload(file, customParams);
},
upload(file: File, customParams = {}) {
return upload(file, customParams);
},
delete(file: File, customParams = {}): Promise<DeleteObjectCommandOutput> {
const command = new DeleteObjectCommand({
Bucket: config.params.Bucket,
Key: getFileKey(file),
...customParams,
});
return s3Client.send(command);
},
};
},
};