Make storage reporting more granular

This commit is contained in:
Sam Becker 2023-11-28 17:53:02 -06:00
parent 5cdb726566
commit 2b9c0cbbe2
4 changed files with 44 additions and 23 deletions

View File

@ -18,16 +18,10 @@ const S3_ADMIN_ACCESS_KEY =
const S3_ADMIN_SECRET_ACCESS_KEY = const S3_ADMIN_SECRET_ACCESS_KEY =
process.env.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({ const client = () => new S3Client({
region: S3_REGION, region: S3_REGION,
credentials: { credentials: {
// Fall back on upload credentials if admin credentials are not available // Fall back on upload credentials when admin credentials aren't available
accessKeyId: S3_ADMIN_ACCESS_KEY ?? S3_UPLOAD_ACCESS_KEY, accessKeyId: S3_ADMIN_ACCESS_KEY ?? S3_UPLOAD_ACCESS_KEY,
secretAccessKey: S3_ADMIN_SECRET_ACCESS_KEY ?? S3_UPLOAD_SECRET_ACCESS_KEY, secretAccessKey: S3_ADMIN_SECRET_ACCESS_KEY ?? S3_UPLOAD_SECRET_ACCESS_KEY,
}, },

View File

@ -7,13 +7,13 @@ import {
} from './vercel-blob'; } from './vercel-blob';
import { import {
AWS_S3_BASE_URL, AWS_S3_BASE_URL,
HAS_AWS_S3_STORAGE,
awsS3Copy, awsS3Copy,
awsS3Delete, awsS3Delete,
awsS3List, awsS3List,
awsS3UploadFromClient, awsS3UploadFromClient,
isUrlFromAwsS3, isUrlFromAwsS3,
} from './aws-s3'; } from './aws-s3';
import { HAS_AWS_S3_STORAGE_CLIENT, HAS_AWS_S3_STORAGE } from '@/site/config';
const PREFIX_UPLOAD = 'upload'; const PREFIX_UPLOAD = 'upload';
const PREFIX_PHOTO = 'photo'; const PREFIX_PHOTO = 'photo';
@ -47,7 +47,7 @@ const getFileNameFromBlobUrl = (url: string) =>
export const uploadPhotoFromClient = async ( export const uploadPhotoFromClient = async (
file: File | Blob, file: File | Blob,
extension = 'jpg', extension = 'jpg',
) => HAS_AWS_S3_STORAGE ) => HAS_AWS_S3_STORAGE_CLIENT
? awsS3UploadFromClient(file, PREFIX_UPLOAD, extension, true) ? awsS3UploadFromClient(file, PREFIX_UPLOAD, extension, true)
: vercelBlobUploadFromClient(file, `${PREFIX_UPLOAD}.${extension}`); : vercelBlobUploadFromClient(file, `${PREFIX_UPLOAD}.${extension}`);
@ -59,12 +59,14 @@ export const convertUploadToPhoto = async (
const fileExtension = getExtensionFromBlobUrl(uploadUrl); const fileExtension = getExtensionFromBlobUrl(uploadUrl);
const photoUrl = `${fileName}.${fileExtension ?? 'jpg'}`; const photoUrl = `${fileName}.${fileExtension ?? 'jpg'}`;
const url = await (HAS_AWS_S3_STORAGE const useAwsS3 = HAS_AWS_S3_STORAGE && isUrlFromAwsS3(uploadUrl);
const url = await (useAwsS3
? awsS3Copy(uploadUrl, photoUrl, photoId === undefined) ? awsS3Copy(uploadUrl, photoUrl, photoId === undefined)
: vercelBlobCopy(uploadUrl, photoUrl, photoId === undefined)); : vercelBlobCopy(uploadUrl, photoUrl, photoId === undefined));
if (url) { if (url) {
await (HAS_AWS_S3_STORAGE await (useAwsS3
? awsS3Delete(getFileNameFromBlobUrl(uploadUrl)) ? awsS3Delete(getFileNameFromBlobUrl(uploadUrl))
: vercelBlobDelete(uploadUrl)); : vercelBlobDelete(uploadUrl));
} }

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useTransition } from 'react'; import { ComponentProps, ReactNode, useTransition } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { cc } from '@/utility/css'; import { cc } from '@/utility/css';
import ChecklistRow from '../components/ChecklistRow'; import ChecklistRow from '../components/ChecklistRow';
@ -18,10 +18,13 @@ import InfoBlock from '@/components/InfoBlock';
import Checklist from '@/components/Checklist'; import Checklist from '@/components/Checklist';
import { toastSuccess } from '@/toast'; import { toastSuccess } from '@/toast';
import { ConfigChecklistStatus } from './config'; import { ConfigChecklistStatus } from './config';
import StatusIcon from '@/components/StatusIcon';
export default function SiteChecklistClient({ export default function SiteChecklistClient({
hasPostgres, hasPostgres,
hasBlob, hasBlob,
hasVercelBlob,
hasAwsS3Storage,
hasAuth, hasAuth,
hasAdminUser, hasAdminUser,
hasTitle, hasTitle,
@ -102,6 +105,17 @@ export default function SiteChecklistClient({
{variables.map(renderEnvVar)} {variables.map(renderEnvVar)}
</div>; </div>;
const renderSubStatus = (
type: ComponentProps<typeof StatusIcon>['type'],
label: ReactNode,
) =>
<div className="flex gap-1.5">
<StatusIcon {...{ type }} />
<span>
{label}
</span>
</div>;
return ( return (
<div className="text-sm max-w-xl space-y-6 w-full"> <div className="text-sm max-w-xl space-y-6 w-full">
<Checklist <Checklist
@ -125,8 +139,9 @@ export default function SiteChecklistClient({
status={hasBlob} status={hasBlob}
isPending={isPendingPage} isPending={isPendingPage}
> >
<ol className="list-decimal list-inside"> {renderSubStatus(
<li> hasVercelBlob ? 'checked' : 'optional',
<>
Vercel Blob: Vercel Blob:
{' '} {' '}
{renderLink( {renderLink(
@ -135,16 +150,19 @@ export default function SiteChecklistClient({
)} )}
{' '} {' '}
and connect to project and connect to project
</li> </>,
<li> )}
{renderSubStatus(
hasAwsS3Storage ? 'checked' : 'optional',
<>
AWS S3: AWS S3:
{' '} {' '}
{renderLink( {renderLink(
'https://github.com/sambecker/exif-photo-blog#aws-s3', 'https://github.com/sambecker/exif-photo-blog#aws-s3',
'create/configure bucket', 'create/configure bucket',
)} )}
</li> </>
</ol> )}
</ChecklistRow> </ChecklistRow>
</Checklist> </Checklist>
<Checklist <Checklist

View File

@ -30,14 +30,19 @@ export const BASE_URL = process.env.NODE_ENV === 'production'
? makeUrlAbsolute(SITE_DOMAIN).toLowerCase() ? makeUrlAbsolute(SITE_DOMAIN).toLowerCase()
: 'http://localhost:3000'; : 'http://localhost:3000';
// STORAGE // STORAGE: VERCEL BLOB
export const HAS_VERCEL_BLOB =
(process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0;
const hasVercelBlob = (process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0; // STORAGE: AWS S3
const hasAwsS3Storage = // Includes separate check for client-side usage, i.e., uploading,
export const HAS_AWS_S3_STORAGE_CLIENT =
(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_KEY ?? '').length > 0 && (process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY ?? '').length > 0 &&
(process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY ?? '').length > 0 && (process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY ?? '').length > 0;
export const HAS_AWS_S3_STORAGE =
HAS_AWS_S3_STORAGE_CLIENT &&
(process.env.S3_ADMIN_ACCESS_KEY ?? '').length > 0 && (process.env.S3_ADMIN_ACCESS_KEY ?? '').length > 0 &&
(process.env.S3_ADMIN_SECRET_ACCESS_KEY ?? '').length > 0; (process.env.S3_ADMIN_SECRET_ACCESS_KEY ?? '').length > 0;
@ -54,7 +59,9 @@ export const OG_TEXT_BOTTOM_ALIGNMENT =
export const CONFIG_CHECKLIST_STATUS = { export const CONFIG_CHECKLIST_STATUS = {
hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0, hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0,
hasBlob: hasVercelBlob || hasAwsS3Storage, hasBlob: HAS_VERCEL_BLOB || HAS_AWS_S3_STORAGE,
hasVercelBlob: HAS_VERCEL_BLOB,
hasAwsS3Storage: HAS_AWS_S3_STORAGE,
hasAuth: (process.env.AUTH_SECRET ?? '').length > 0, hasAuth: (process.env.AUTH_SECRET ?? '').length > 0,
hasAdminUser: ( hasAdminUser: (
(process.env.ADMIN_EMAIL ?? '').length > 0 && (process.env.ADMIN_EMAIL ?? '').length > 0 &&