Sort admin uploads by date
This commit is contained in:
parent
928f9cb10e
commit
d771b22302
@ -2,24 +2,25 @@ import { Fragment } from 'react';
|
||||
import AdminGrid from './AdminGrid';
|
||||
import Link from 'next/link';
|
||||
import ImageTiny from '@/components/ImageTiny';
|
||||
import { fileNameForStorageUrl } from '@/services/storage';
|
||||
import { StorageListResponse, fileNameForStorageUrl } from '@/services/storage';
|
||||
import FormWithConfirm from '@/components/FormWithConfirm';
|
||||
import { deleteBlobPhotoAction } from '@/photo/actions';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import { clsx } from 'clsx/lite';
|
||||
import { pathForAdminUploadUrl } from '@/site/paths';
|
||||
import AddButton from './AddButton';
|
||||
import { formatDate } from 'date-fns';
|
||||
|
||||
export default function StorageUrls({
|
||||
title,
|
||||
urls,
|
||||
}: {
|
||||
title?: string
|
||||
urls: string[]
|
||||
urls: StorageListResponse
|
||||
}) {
|
||||
return (
|
||||
<AdminGrid {...{ title }} >
|
||||
{urls.map(url => {
|
||||
{urls.map(({ url, uploadedAt }) => {
|
||||
const addUploadPath = pathForAdminUploadUrl(url);
|
||||
const uploadFileName = fileNameForStorageUrl(url);
|
||||
return <Fragment key={url}>
|
||||
@ -37,7 +38,9 @@ export default function StorageUrls({
|
||||
<Link
|
||||
href={addUploadPath}
|
||||
className="break-all"
|
||||
title={url}
|
||||
title={uploadedAt
|
||||
? `${url} @ ${formatDate(uploadedAt, 'yyyy-MM-dd HH:mm:ss')}`
|
||||
: url}
|
||||
>
|
||||
{uploadFileName}
|
||||
</Link>
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
ListObjectsCommand,
|
||||
PutObjectCommand,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { generateStorageId } from '.';
|
||||
import { StorageListResponse, generateStorageId } from '.';
|
||||
|
||||
const AWS_S3_BUCKET = process.env.NEXT_PUBLIC_AWS_S3_BUCKET ?? '';
|
||||
const AWS_S3_REGION = process.env.NEXT_PUBLIC_AWS_S3_REGION ?? '';
|
||||
@ -26,8 +26,8 @@ export const awsS3Client = () => new S3Client({
|
||||
|
||||
const urlForKey = (key?: string) => `${AWS_S3_BASE_URL}/${key}`;
|
||||
|
||||
export const isUrlFromAwsS3 = (url: string) =>
|
||||
AWS_S3_BASE_URL && url.startsWith(AWS_S3_BASE_URL);
|
||||
export const isUrlFromAwsS3 = (url?: string) =>
|
||||
AWS_S3_BASE_URL && url?.startsWith(AWS_S3_BASE_URL);
|
||||
|
||||
export const awsS3PutObjectCommandForKey = (Key: string) =>
|
||||
new PutObjectCommand({ Bucket: AWS_S3_BUCKET, Key, ACL: 'public-read' });
|
||||
@ -51,12 +51,17 @@ export const awsS3Copy = async (
|
||||
.then(() => urlForKey(fileNameDestination));
|
||||
};
|
||||
|
||||
export const awsS3List = async (Prefix: string) =>
|
||||
export const awsS3List = async (
|
||||
Prefix: string,
|
||||
): Promise<StorageListResponse> =>
|
||||
awsS3Client().send(new ListObjectsCommand({
|
||||
Bucket: AWS_S3_BUCKET,
|
||||
Prefix,
|
||||
}))
|
||||
.then((data) => data.Contents?.map(({ Key }) => urlForKey(Key)) ?? []);
|
||||
.then((data) => data.Contents?.map(({ Key, LastModified }) => ({
|
||||
url: urlForKey(Key),
|
||||
uploadedAt: LastModified,
|
||||
})) ?? []);
|
||||
|
||||
export const awsS3Delete = async (Key: string) => {
|
||||
awsS3Client().send(new DeleteObjectCommand({
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
DeleteObjectCommand,
|
||||
CopyObjectCommand,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { generateStorageId } from '.';
|
||||
import { StorageListResponse, generateStorageId } from '.';
|
||||
|
||||
const CLOUDFLARE_R2_BUCKET =
|
||||
process.env.NEXT_PUBLIC_CLOUDFLARE_R2_BUCKET ?? '';
|
||||
@ -42,12 +42,12 @@ const urlForKey = (key?: string, isPublic = true) => isPublic
|
||||
? `${CLOUDFLARE_R2_BASE_URL_PUBLIC}/${key}`
|
||||
: `${CLOUDFLARE_R2_BASE_URL_PRIVATE}/${key}`;
|
||||
|
||||
export const isUrlFromCloudflareR2 = (url: string) => (
|
||||
export const isUrlFromCloudflareR2 = (url?: string) => (
|
||||
CLOUDFLARE_R2_BASE_URL_PRIVATE &&
|
||||
url.startsWith(CLOUDFLARE_R2_BASE_URL_PRIVATE)
|
||||
url?.startsWith(CLOUDFLARE_R2_BASE_URL_PRIVATE)
|
||||
) || (
|
||||
CLOUDFLARE_R2_BASE_URL_PUBLIC &&
|
||||
url.startsWith(CLOUDFLARE_R2_BASE_URL_PUBLIC)
|
||||
url?.startsWith(CLOUDFLARE_R2_BASE_URL_PUBLIC)
|
||||
);
|
||||
|
||||
export const cloudflareR2PutObjectCommandForKey = (Key: string) =>
|
||||
@ -71,12 +71,17 @@ export const cloudflareR2Copy = async (
|
||||
.then(() => urlForKey(fileNameDestination));
|
||||
};
|
||||
|
||||
export const cloudflareR2List = async (Prefix: string) =>
|
||||
export const cloudflareR2List = async (
|
||||
Prefix: string,
|
||||
): Promise<StorageListResponse> =>
|
||||
cloudflareR2Client().send(new ListObjectsCommand({
|
||||
Bucket: CLOUDFLARE_R2_BUCKET,
|
||||
Prefix,
|
||||
}))
|
||||
.then((data) => data.Contents?.map(({ Key }) => urlForKey(Key)) ?? []);
|
||||
.then((data) => data.Contents?.map(({ Key, LastModified }) => ({
|
||||
url: urlForKey(Key),
|
||||
uploadedAt: LastModified,
|
||||
})) ?? []);
|
||||
|
||||
export const cloudflareR2Delete = async (Key: string) => {
|
||||
cloudflareR2Client().send(new DeleteObjectCommand({
|
||||
|
||||
@ -30,6 +30,11 @@ import { PATH_API_PRESIGNED_URL } from '@/site/paths';
|
||||
|
||||
export const generateStorageId = () => generateNanoid(16);
|
||||
|
||||
export type StorageListResponse = {
|
||||
url: string
|
||||
uploadedAt?: Date
|
||||
}[];
|
||||
|
||||
export type StorageType =
|
||||
'vercel-blob' |
|
||||
'aws-s3' |
|
||||
@ -182,8 +187,8 @@ export const deleteStorageUrl = (url: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getStorageUrlsForPrefix = async (prefix = ''): Promise<string[]> => {
|
||||
const urls: string[] = [];
|
||||
const getStorageUrlsForPrefix = async (prefix = '') => {
|
||||
const urls: StorageListResponse = [];
|
||||
|
||||
if (HAS_VERCEL_BLOB_STORAGE) {
|
||||
urls.push(...await vercelBlobList(prefix));
|
||||
@ -195,7 +200,12 @@ const getStorageUrlsForPrefix = async (prefix = ''): Promise<string[]> => {
|
||||
urls.push(...await cloudflareR2List(prefix));
|
||||
}
|
||||
|
||||
return urls;
|
||||
return urls
|
||||
.sort((a, b) => {
|
||||
if (!a.uploadedAt) { return 1; }
|
||||
if (!b.uploadedAt) { return -1; }
|
||||
return b.uploadedAt.getTime() - a.uploadedAt.getTime();
|
||||
});
|
||||
};
|
||||
|
||||
export const getStorageUploadUrls = () =>
|
||||
|
||||
@ -10,9 +10,9 @@ export const VERCEL_BLOB_BASE_URL = VERCEL_BLOB_STORE_ID
|
||||
? `https://${VERCEL_BLOB_STORE_ID}.public.blob.vercel-storage.com`
|
||||
: undefined;
|
||||
|
||||
export const isUrlFromVercelBlob = (url: string) =>
|
||||
export const isUrlFromVercelBlob = (url?: string) =>
|
||||
VERCEL_BLOB_BASE_URL &&
|
||||
url.startsWith(VERCEL_BLOB_BASE_URL);
|
||||
url?.startsWith(VERCEL_BLOB_BASE_URL);
|
||||
|
||||
export const vercelBlobUploadFromClient = async (
|
||||
file: File | Blob,
|
||||
@ -46,4 +46,7 @@ export const vercelBlobCopy = (
|
||||
export const vercelBlobDelete = (fileName: string) => del(fileName);
|
||||
|
||||
export const vercelBlobList = (prefix: string) => list({ prefix })
|
||||
.then(({ blobs }) => blobs.map(({ url }) => url));
|
||||
.then(({ blobs }) => blobs.map(({ url, uploadedAt }) => ({
|
||||
url,
|
||||
uploadedAt,
|
||||
})));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user