Abstract blob service, add core S3 functionality
This commit is contained in:
parent
25941329db
commit
fe992c0e17
13
README.md
13
README.md
@ -72,13 +72,14 @@ Installation
|
|||||||
|
|
||||||
#### AWS S3
|
#### AWS S3
|
||||||
|
|
||||||
1. [Create bucket](https://s3.console.aws.amazon.com/s3) with "Block all public access" turned off
|
1. [Create bucket](https://s3.console.aws.amazon.com/s3) with "ACLs enabled," and "Block all public access" turned off
|
||||||
- Setup CORS:
|
- Setup CORS:
|
||||||
```
|
```
|
||||||
[{
|
[{
|
||||||
"AllowedHeaders": [],
|
"AllowedHeaders": ["*"],
|
||||||
"AllowedMethods": [
|
"AllowedMethods": [
|
||||||
"GET"
|
"GET",
|
||||||
|
"PUT"
|
||||||
],
|
],
|
||||||
"AllowedOrigins": [
|
"AllowedOrigins": [
|
||||||
"http://localhost:*",
|
"http://localhost:*",
|
||||||
@ -92,10 +93,10 @@ Installation
|
|||||||
- `NEXT_PUBLIC_S3_BUCKET`
|
- `NEXT_PUBLIC_S3_BUCKET`
|
||||||
- `NEXT_PUBLIC_S3_REGION`
|
- `NEXT_PUBLIC_S3_REGION`
|
||||||
2. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for client uploads (JSON editor recommended)
|
2. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for client uploads (JSON editor recommended)
|
||||||
- Action: `s3:PutObject`
|
- Action: `s3:PutObject`, `s3:PutObjectACL`
|
||||||
- Resource: `arn:aws:s3:::{BUCKET_NAME}/uploads/*`
|
- Resource: `arn:aws:s3:::{BUCKET_NAME}/upload-*`
|
||||||
3. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for admin actions (JSON editor recommended)
|
3. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for admin actions (JSON editor recommended)
|
||||||
- Action: `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject`
|
- Action: `s3:PutObject`, `s3:PutObjectACL`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject`
|
||||||
- Resource: `arn:aws:s3:::{BUCKET_NAME}`, `arn:aws:s3:::{BUCKET_NAME}/*`
|
- Resource: `arn:aws:s3:::{BUCKET_NAME}`, `arn:aws:s3:::{BUCKET_NAME}/*`
|
||||||
4. [Create IAM user](https://console.aws.amazon.com/iam/home#/users) for upload policy (by choosing "Attach policies directly"), create access key under "Security credentials," choose "Application running outside AWS," and store credentials
|
4. [Create IAM user](https://console.aws.amazon.com/iam/home#/users) for upload policy (by choosing "Attach policies directly"), create access key under "Security credentials," choose "Application running outside AWS," and store credentials
|
||||||
- `NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY`
|
- `NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY`
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"analyze": "ANALYZE=true next build"
|
"analyze": "ANALYZE=true next build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.456.0",
|
||||||
"@next/bundle-analyzer": "14.0.3",
|
"@next/bundle-analyzer": "14.0.3",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@testing-library/jest-dom": "^6.1.4",
|
"@testing-library/jest-dom": "^6.1.4",
|
||||||
|
|||||||
1047
pnpm-lock.yaml
generated
1047
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
src/cache/index.ts
vendored
2
src/cache/index.ts
vendored
@ -20,7 +20,7 @@ import {
|
|||||||
getUniqueFilmSimulations,
|
getUniqueFilmSimulations,
|
||||||
getPhotosFilmSimulationDateRange,
|
getPhotosFilmSimulationDateRange,
|
||||||
getPhotosFilmSimulationCount,
|
getPhotosFilmSimulationCount,
|
||||||
} from '@/services/postgres';
|
} from '@/services/vercel-postgres';
|
||||||
import { parseCachedPhotoDates, parseCachedPhotosDates } from '@/photo';
|
import { parseCachedPhotoDates, parseCachedPhotosDates } from '@/photo';
|
||||||
import { getBlobPhotoUrls, getBlobUploadUrls } from '@/services/blob';
|
import { getBlobPhotoUrls, getBlobUploadUrls } from '@/services/blob';
|
||||||
import type { Session } from 'next-auth';
|
import type { Session } from 'next-auth';
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export default function PhotoUpload({
|
|||||||
blob,
|
blob,
|
||||||
extension,
|
extension,
|
||||||
)
|
)
|
||||||
.then(({ url }) => {
|
.then(url => {
|
||||||
if (isLastBlob) {
|
if (isLastBlob) {
|
||||||
// Refresh page to update upload list,
|
// Refresh page to update upload list,
|
||||||
// relevant to upload count in nav
|
// relevant to upload count in nav
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
sqlUpdatePhoto,
|
sqlUpdatePhoto,
|
||||||
sqlRenamePhotoTagGlobally,
|
sqlRenamePhotoTagGlobally,
|
||||||
getPhoto,
|
getPhoto,
|
||||||
} from '@/services/postgres';
|
} from '@/services/vercel-postgres';
|
||||||
import {
|
import {
|
||||||
PhotoFormData,
|
PhotoFormData,
|
||||||
convertFormDataToPhotoDbInsert,
|
convertFormDataToPhotoDbInsert,
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
convertUploadToPhoto,
|
convertUploadToPhoto,
|
||||||
deleteBlobPhoto,
|
deleteBlobUrl,
|
||||||
} from '@/services/blob';
|
} from '@/services/blob';
|
||||||
import {
|
import {
|
||||||
revalidateAdminPaths,
|
revalidateAdminPaths,
|
||||||
@ -52,7 +52,7 @@ export async function updatePhotoAction(formData: FormData) {
|
|||||||
|
|
||||||
export async function deletePhotoAction(formData: FormData) {
|
export async function deletePhotoAction(formData: FormData) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
deleteBlobPhoto(formData.get('url') as string),
|
deleteBlobUrl(formData.get('url') as string),
|
||||||
sqlDeletePhoto(formData.get('id') as string),
|
sqlDeletePhoto(formData.get('id') as string),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export async function renamePhotoTagGloballyAction(formData: FormData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBlobPhotoAction(formData: FormData) {
|
export async function deleteBlobPhotoAction(formData: FormData) {
|
||||||
await deleteBlobPhoto(formData.get('url') as string);
|
await deleteBlobUrl(formData.get('url') as string);
|
||||||
|
|
||||||
revalidateAdminPaths();
|
revalidateAdminPaths();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import { getExtensionFromBlobUrl, getIdFromBlobUrl } from '@/services/blob';
|
import {
|
||||||
|
getExtensionFromBlobUrl,
|
||||||
|
getIdFromBlobUrl,
|
||||||
|
} from '@/services/blob';
|
||||||
import { convertExifToFormData } from '@/photo/form';
|
import { convertExifToFormData } from '@/photo/form';
|
||||||
import {
|
import {
|
||||||
getFujifilmSimulationFromMakerNote,
|
getFujifilmSimulationFromMakerNote,
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
import { PATH_ADMIN_UPLOAD_BLOB } from '@/site/paths';
|
|
||||||
import { copy, del, list } from '@vercel/blob';
|
|
||||||
import { upload } from '@vercel/blob/client';
|
|
||||||
|
|
||||||
const STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match(
|
|
||||||
/^vercel_blob_rw_([a-z0-9]+)_[a-z0-9]+$/i,
|
|
||||||
)?.[1].toLowerCase();
|
|
||||||
|
|
||||||
export const BLOB_BASE_URL =
|
|
||||||
`https://${STORE_ID}.public.blob.vercel-storage.com`;
|
|
||||||
|
|
||||||
const PREFIX_UPLOAD = 'upload';
|
|
||||||
const PREFIX_PHOTO = 'photo';
|
|
||||||
|
|
||||||
const REGEX_UPLOAD_PATH = new RegExp(
|
|
||||||
`(?:${PREFIX_UPLOAD})\.[a-z]{1,4}`,
|
|
||||||
'i',
|
|
||||||
);
|
|
||||||
|
|
||||||
const REGEX_UPLOAD_ID = new RegExp(
|
|
||||||
`.${PREFIX_UPLOAD}-([a-z0-9]+)\.[a-z]{1,4}$`,
|
|
||||||
'i',
|
|
||||||
);
|
|
||||||
|
|
||||||
export const pathForBlobUrl = (url: string) =>
|
|
||||||
url.replace(`${BLOB_BASE_URL}/`, '');
|
|
||||||
|
|
||||||
export const getExtensionFromBlobUrl = (url: string) =>
|
|
||||||
url.match(/.([a-z]{1,4})$/i)?.[1];
|
|
||||||
|
|
||||||
export const getIdFromBlobUrl = (url: string) =>
|
|
||||||
url.match(REGEX_UPLOAD_ID)?.[1];
|
|
||||||
|
|
||||||
export const isUploadPathnameValid = (pathname?: string) =>
|
|
||||||
pathname?.match(REGEX_UPLOAD_PATH);
|
|
||||||
|
|
||||||
export const uploadPhotoFromClient = async (
|
|
||||||
file: File | Blob,
|
|
||||||
extension = 'jpg',
|
|
||||||
) =>
|
|
||||||
upload(
|
|
||||||
`${PREFIX_UPLOAD}.${extension}`,
|
|
||||||
file,
|
|
||||||
{
|
|
||||||
access: 'public',
|
|
||||||
handleUploadUrl: PATH_ADMIN_UPLOAD_BLOB,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const convertUploadToPhoto = async (
|
|
||||||
uploadUrl: string,
|
|
||||||
photoId?: string,
|
|
||||||
) => {
|
|
||||||
const fileName = photoId ? `${PREFIX_PHOTO}-${photoId}` : `${PREFIX_PHOTO}`;
|
|
||||||
const fileExtension = getExtensionFromBlobUrl(uploadUrl) ?? 'jpg';
|
|
||||||
const photoUrl = `${fileName}.${fileExtension ?? 'jpg'}`;
|
|
||||||
|
|
||||||
const { url } = await copy(
|
|
||||||
uploadUrl,
|
|
||||||
photoUrl,
|
|
||||||
{
|
|
||||||
access: 'public',
|
|
||||||
...photoId && { addRandomSuffix: false },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
await del(uploadUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteBlobPhoto = (url: string) => del(url);
|
|
||||||
|
|
||||||
export const getBlobUploadUrls = () =>
|
|
||||||
list({ prefix: `${PREFIX_UPLOAD}-` })
|
|
||||||
.then(({ blobs }) => blobs.map(({ url }) => url));
|
|
||||||
|
|
||||||
export const getBlobPhotoUrls = () =>
|
|
||||||
list({ prefix: `${PREFIX_PHOTO}-` })
|
|
||||||
.then(({ blobs }) => blobs.map(({ url }) => url));
|
|
||||||
93
src/services/blob/aws-s3.ts
Normal file
93
src/services/blob/aws-s3.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { generateNanoid } from '@/utility/nanoid';
|
||||||
|
import {
|
||||||
|
S3Client,
|
||||||
|
CopyObjectCommand,
|
||||||
|
DeleteObjectCommand,
|
||||||
|
ListObjectsCommand,
|
||||||
|
PutObjectCommand,
|
||||||
|
} from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
|
const S3_BUCKET = process.env.NEXT_PUBLIC_S3_BUCKET ?? '';
|
||||||
|
const S3_REGION = process.env.NEXT_PUBLIC_S3_REGION ?? '';
|
||||||
|
const S3_UPLOAD_ACCESS_KEY =
|
||||||
|
process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY ?? '';
|
||||||
|
const S3_UPLOAD_SECRET_ACCESS_KEY =
|
||||||
|
process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY ?? '';
|
||||||
|
const S3_ADMIN_ACCESS_KEY =
|
||||||
|
process.env.S3_ADMIN_ACCESS_KEY;
|
||||||
|
const S3_ADMIN_SECRET_ACCESS_KEY =
|
||||||
|
process.env.S3_ADMIN_SECRET_ACCESS_KEY;
|
||||||
|
|
||||||
|
export const HAS_AWS_S3_STORAGE =
|
||||||
|
S3_BUCKET.length > 0 &&
|
||||||
|
S3_REGION.length > 0 &&
|
||||||
|
S3_UPLOAD_ACCESS_KEY.length > 0 &&
|
||||||
|
S3_UPLOAD_SECRET_ACCESS_KEY.length > 0;
|
||||||
|
|
||||||
|
const client = new S3Client({
|
||||||
|
region: S3_REGION,
|
||||||
|
credentials: {
|
||||||
|
// Fallback on upload credentials if admin credentials are not available
|
||||||
|
accessKeyId: S3_ADMIN_ACCESS_KEY ?? S3_UPLOAD_ACCESS_KEY,
|
||||||
|
secretAccessKey: S3_ADMIN_SECRET_ACCESS_KEY ?? S3_UPLOAD_SECRET_ACCESS_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AWS_S3_BASE_URL =
|
||||||
|
`https://${S3_BUCKET}.s3.${S3_REGION}.amazonaws.com`;
|
||||||
|
|
||||||
|
export const isUrlFromAwsS3 = (url: string) =>
|
||||||
|
url.startsWith(AWS_S3_BASE_URL);
|
||||||
|
|
||||||
|
const urlForKey = (key?: string) => `${AWS_S3_BASE_URL}/${key}`;
|
||||||
|
|
||||||
|
export const awsS3UploadFromClient = async (
|
||||||
|
file: File | Blob,
|
||||||
|
fileName: string,
|
||||||
|
extension: string,
|
||||||
|
addRandomSuffix?: boolean,
|
||||||
|
) => {
|
||||||
|
const Key = addRandomSuffix
|
||||||
|
? `${fileName}-${generateNanoid()}.${extension}`
|
||||||
|
: `${fileName}.${extension}`;
|
||||||
|
return client.send(new PutObjectCommand({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
Key,
|
||||||
|
Body: file,
|
||||||
|
ACL: 'public-read',
|
||||||
|
}))
|
||||||
|
.then(() => urlForKey(Key));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const awsS3Copy = async (
|
||||||
|
fileNameSource: string,
|
||||||
|
fileNameDestination: string,
|
||||||
|
addRandomSuffix?: boolean,
|
||||||
|
) => {
|
||||||
|
const name = fileNameSource.split('.')[0];
|
||||||
|
const extension = fileNameSource.split('.')[1];
|
||||||
|
const Key = addRandomSuffix
|
||||||
|
? `${name}-${generateNanoid()}.${extension}`
|
||||||
|
: fileNameDestination;
|
||||||
|
return client.send(new CopyObjectCommand({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
CopySource: fileNameSource,
|
||||||
|
Key,
|
||||||
|
ACL: 'public-read',
|
||||||
|
}))
|
||||||
|
.then(() => urlForKey(fileNameDestination));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const awsS3Delete = async (Key: string) => {
|
||||||
|
client.send(new DeleteObjectCommand({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
Key,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const awsS3List = async (Prefix: string) =>
|
||||||
|
client.send(new ListObjectsCommand({
|
||||||
|
Bucket: S3_BUCKET,
|
||||||
|
Prefix,
|
||||||
|
}))
|
||||||
|
.then((data) => data.Contents?.map(({ Key }) => urlForKey(Key)) ?? []);
|
||||||
86
src/services/blob/index.ts
Normal file
86
src/services/blob/index.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
VERCEL_BLOB_BASE_URL,
|
||||||
|
vercelBlobCopy,
|
||||||
|
vercelBlobDelete,
|
||||||
|
vercelBlobList,
|
||||||
|
vercelBlobUploadFromClient,
|
||||||
|
} from './vercel-blob';
|
||||||
|
import {
|
||||||
|
AWS_S3_BASE_URL,
|
||||||
|
HAS_AWS_S3_STORAGE,
|
||||||
|
awsS3Copy,
|
||||||
|
awsS3Delete,
|
||||||
|
awsS3List,
|
||||||
|
awsS3UploadFromClient,
|
||||||
|
isUrlFromAwsS3,
|
||||||
|
} from './aws-s3';
|
||||||
|
|
||||||
|
const PREFIX_UPLOAD = 'upload';
|
||||||
|
const PREFIX_PHOTO = 'photo';
|
||||||
|
const BLOB_BASE_URL = AWS_S3_BASE_URL ?? VERCEL_BLOB_BASE_URL;
|
||||||
|
|
||||||
|
const REGEX_UPLOAD_PATH = new RegExp(
|
||||||
|
`(?:${PREFIX_UPLOAD})\.[a-z]{1,4}`,
|
||||||
|
'i',
|
||||||
|
);
|
||||||
|
|
||||||
|
const REGEX_UPLOAD_ID = new RegExp(
|
||||||
|
`.${PREFIX_UPLOAD}-([a-z0-9]+)\.[a-z]{1,4}$`,
|
||||||
|
'i',
|
||||||
|
);
|
||||||
|
|
||||||
|
export const pathForBlobUrl = (url: string) =>
|
||||||
|
url.replace(`${BLOB_BASE_URL}/`, '');
|
||||||
|
|
||||||
|
export const getExtensionFromBlobUrl = (url: string) =>
|
||||||
|
url.match(/.([a-z]{1,4})$/i)?.[1];
|
||||||
|
|
||||||
|
export const getIdFromBlobUrl = (url: string) =>
|
||||||
|
url.match(REGEX_UPLOAD_ID)?.[1];
|
||||||
|
|
||||||
|
export const isUploadPathnameValid = (pathname?: string) =>
|
||||||
|
pathname?.match(REGEX_UPLOAD_PATH);
|
||||||
|
|
||||||
|
const getFileNameFromBlobUrl = (url: string) =>
|
||||||
|
(new URL(url).pathname.match(/\/(.+)$/)?.[1]) ?? '';
|
||||||
|
|
||||||
|
export const uploadPhotoFromClient = async (
|
||||||
|
file: File | Blob,
|
||||||
|
extension = 'jpg',
|
||||||
|
) => HAS_AWS_S3_STORAGE
|
||||||
|
? awsS3UploadFromClient(file, PREFIX_UPLOAD, extension, true)
|
||||||
|
: vercelBlobUploadFromClient(file, `${PREFIX_UPLOAD}.${extension}`);
|
||||||
|
|
||||||
|
export const convertUploadToPhoto = async (
|
||||||
|
uploadUrl: string,
|
||||||
|
photoId?: string,
|
||||||
|
): Promise<string> => {
|
||||||
|
const fileName = photoId ? `${PREFIX_PHOTO}-${photoId}` : `${PREFIX_PHOTO}`;
|
||||||
|
const fileExtension = getExtensionFromBlobUrl(uploadUrl);
|
||||||
|
const photoUrl = `${fileName}.${fileExtension ?? 'jpg'}`;
|
||||||
|
|
||||||
|
const url = await (HAS_AWS_S3_STORAGE
|
||||||
|
? awsS3Copy(uploadUrl, photoUrl, photoId === undefined)
|
||||||
|
: vercelBlobCopy(uploadUrl, photoUrl, photoId === undefined));
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
await (HAS_AWS_S3_STORAGE
|
||||||
|
? awsS3Delete(getFileNameFromBlobUrl(uploadUrl))
|
||||||
|
: vercelBlobDelete(uploadUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteBlobUrl = (url: string) =>
|
||||||
|
HAS_AWS_S3_STORAGE && isUrlFromAwsS3(url)
|
||||||
|
? awsS3Delete(getFileNameFromBlobUrl(url))
|
||||||
|
: vercelBlobDelete(url);
|
||||||
|
|
||||||
|
export const getBlobUploadUrls = (): Promise<string[]> => HAS_AWS_S3_STORAGE
|
||||||
|
? awsS3List(`${PREFIX_UPLOAD}-`)
|
||||||
|
: vercelBlobList(`${PREFIX_UPLOAD}-`);
|
||||||
|
|
||||||
|
export const getBlobPhotoUrls = (): Promise<string[]> => HAS_AWS_S3_STORAGE
|
||||||
|
? awsS3List(`${PREFIX_PHOTO}-`)
|
||||||
|
: vercelBlobList(`${PREFIX_PHOTO}-`);
|
||||||
44
src/services/blob/vercel-blob.ts
Normal file
44
src/services/blob/vercel-blob.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { PATH_ADMIN_UPLOAD_BLOB } from '@/site/paths';
|
||||||
|
import { copy, del, list } from '@vercel/blob';
|
||||||
|
import { upload } from '@vercel/blob/client';
|
||||||
|
|
||||||
|
const VERCEL_BLOB_STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match(
|
||||||
|
/^vercel_blob_rw_([a-z0-9]+)_[a-z0-9]+$/i,
|
||||||
|
)?.[1].toLowerCase();
|
||||||
|
|
||||||
|
export const VERCEL_BLOB_BASE_URL =
|
||||||
|
`https://${VERCEL_BLOB_STORE_ID}.public.blob.vercel-storage.com`;
|
||||||
|
|
||||||
|
export const vercelBlobUploadFromClient = async (
|
||||||
|
file: File | Blob,
|
||||||
|
fileName: string,
|
||||||
|
) =>
|
||||||
|
upload(
|
||||||
|
fileName,
|
||||||
|
file,
|
||||||
|
{
|
||||||
|
access: 'public',
|
||||||
|
handleUploadUrl: PATH_ADMIN_UPLOAD_BLOB,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(({ url }) => url);
|
||||||
|
|
||||||
|
export const vercelBlobCopy = (
|
||||||
|
fileNameSource: string,
|
||||||
|
fileNameDestination: string,
|
||||||
|
addRandomSuffix?: boolean,
|
||||||
|
): Promise<string> =>
|
||||||
|
copy(
|
||||||
|
fileNameSource,
|
||||||
|
fileNameDestination,
|
||||||
|
{
|
||||||
|
access: 'public',
|
||||||
|
addRandomSuffix,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then(({ url }) => url);
|
||||||
|
|
||||||
|
export const vercelBlobDelete = (fileName: string) => del(fileName);
|
||||||
|
|
||||||
|
export const vercelBlobList = (prefix: string) => list({ prefix })
|
||||||
|
.then(({ blobs }) => blobs.map(({ url }) => url));
|
||||||
@ -36,10 +36,10 @@ const hasVercelBlob = (process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0;
|
|||||||
const hasAwsS3Storage =
|
const hasAwsS3Storage =
|
||||||
(process.env.NEXT_PUBLIC_S3_BUCKET ?? '').length > 0 &&
|
(process.env.NEXT_PUBLIC_S3_BUCKET ?? '').length > 0 &&
|
||||||
(process.env.NEXT_PUBLIC_S3_REGION ?? '').length > 0 &&
|
(process.env.NEXT_PUBLIC_S3_REGION ?? '').length > 0 &&
|
||||||
(process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_ID ?? '').length > 0 &&
|
(process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY ?? '').length > 0 &&
|
||||||
(process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET ?? '').length > 0 &&
|
(process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY ?? '').length > 0 &&
|
||||||
(process.env.S3_ADMIN_ACCESS_ID ?? '').length > 0 &&
|
(process.env.S3_ADMIN_ACCESS_KEY ?? '').length > 0 &&
|
||||||
(process.env.S3_ADMIN_ACCESS_SECRET ?? '').length > 0;
|
(process.env.S3_ADMIN_SECRET_ACCESS_KEY ?? '').length > 0;
|
||||||
|
|
||||||
|
|
||||||
// SETTINGS
|
// SETTINGS
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user