Add AI rate limiting and safety documentation
This commit is contained in:
parent
340c2f879a
commit
786378e4a5
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -24,6 +24,7 @@
|
|||||||
"jpgs",
|
"jpgs",
|
||||||
"Lightbox",
|
"Lightbox",
|
||||||
"Makernote",
|
"Makernote",
|
||||||
|
"mitigations",
|
||||||
"nanoids",
|
"nanoids",
|
||||||
"nextjs",
|
"nextjs",
|
||||||
"parameterizes",
|
"parameterizes",
|
||||||
@ -31,6 +32,8 @@
|
|||||||
"Provia",
|
"Provia",
|
||||||
"qaub",
|
"qaub",
|
||||||
"QRSTUVWXYZ",
|
"QRSTUVWXYZ",
|
||||||
|
"ratelimit",
|
||||||
|
"ratelimiter",
|
||||||
"Reala",
|
"Reala",
|
||||||
"skippable",
|
"skippable",
|
||||||
"sonner",
|
"sonner",
|
||||||
@ -38,6 +41,7 @@
|
|||||||
"thephotoblog",
|
"thephotoblog",
|
||||||
"trpc",
|
"trpc",
|
||||||
"unnest",
|
"unnest",
|
||||||
|
"upstash",
|
||||||
"UsKSGcbt",
|
"UsKSGcbt",
|
||||||
"Velvia",
|
"Velvia",
|
||||||
"WRHGZC",
|
"WRHGZC",
|
||||||
|
|||||||
17
README.md
17
README.md
@ -68,12 +68,25 @@ Installation
|
|||||||
2. Click "Speed Insights" tab
|
2. Click "Speed Insights" tab
|
||||||
3. Follow "Enable Speed Insights" instructions (`@vercel/speed-insights` already included)
|
3. Follow "Enable Speed Insights" instructions (`@vercel/speed-insights` already included)
|
||||||
|
|
||||||
### 7. Optional configuration
|
### 7. Add experimental AI text generation
|
||||||
|
|
||||||
|
_⚠️ READ BEFORE PROCEEDING_
|
||||||
|
- _Usage of this feature will result in fees from OpenAI._
|
||||||
|
- _When enabling AI text generation, follow all recommended mitigations in order to avoid unexpected charges and attacks._
|
||||||
|
- _Make sure your OpenAI secret key is not prefixed with NEXT_PUBLIC._
|
||||||
|
|
||||||
|
1. Setup OpenAI
|
||||||
|
- If you don't already have one, create an [OpenAI](https://openai.com) account
|
||||||
|
- Generate an API key and store as `OPENAI_SECRET_KEY`
|
||||||
|
- Setup usage limits to avoid unexpected charges (_recommended_)
|
||||||
|
2. Add rate limiting (_recommended_)
|
||||||
|
- As an additional precaution, create a [Vercel KV](https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database) store and link it to your project in order to enable rate limiting
|
||||||
|
|
||||||
|
### 8. Optional configuration
|
||||||
|
|
||||||
- `NEXT_PUBLIC_PRO_MODE = 1` enables higher quality image storage for jpgs (will result in increased storage usage)
|
- `NEXT_PUBLIC_PRO_MODE = 1` enables higher quality image storage for jpgs (will result in increased storage usage)
|
||||||
- `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage)
|
- `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage)
|
||||||
- `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data
|
- `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data
|
||||||
- `OPENAI_SECRET_KEY = [Your Key]` enables experimental support for AI-generated text descriptions
|
|
||||||
- `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order
|
- `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order
|
||||||
- `NEXT_PUBLIC_PUBLIC_API = 1` enables public API available at `/api`
|
- `NEXT_PUBLIC_PUBLIC_API = 1` enables public API available at `/api`
|
||||||
- `NEXT_PUBLIC_HIDE_REPO_LINK = 1` removes footer link to repo
|
- `NEXT_PUBLIC_HIDE_REPO_LINK = 1` removes footer link to repo
|
||||||
|
|||||||
@ -23,8 +23,10 @@
|
|||||||
"@types/react-dom": "18.2.21",
|
"@types/react-dom": "18.2.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
"@typescript-eslint/parser": "^7.2.0",
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
|
"@upstash/ratelimit": "^1.0.1",
|
||||||
"@vercel/analytics": "^1.2.2",
|
"@vercel/analytics": "^1.2.2",
|
||||||
"@vercel/blob": "^0.22.1",
|
"@vercel/blob": "^0.22.1",
|
||||||
|
"@vercel/kv": "^1.0.1",
|
||||||
"@vercel/postgres": "0.7.2",
|
"@vercel/postgres": "0.7.2",
|
||||||
"@vercel/speed-insights": "^1.0.10",
|
"@vercel/speed-insights": "^1.0.10",
|
||||||
"ai": "^3.0.13",
|
"ai": "^3.0.13",
|
||||||
|
|||||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@ -47,12 +47,18 @@ dependencies:
|
|||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: ^7.2.0
|
specifier: ^7.2.0
|
||||||
version: 7.2.0(eslint@8.57.0)(typescript@5.4.2)
|
version: 7.2.0(eslint@8.57.0)(typescript@5.4.2)
|
||||||
|
'@upstash/ratelimit':
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
'@vercel/analytics':
|
'@vercel/analytics':
|
||||||
specifier: ^1.2.2
|
specifier: ^1.2.2
|
||||||
version: 1.2.2(next@14.1.4)(react@18.2.0)
|
version: 1.2.2(next@14.1.4)(react@18.2.0)
|
||||||
'@vercel/blob':
|
'@vercel/blob':
|
||||||
specifier: ^0.22.1
|
specifier: ^0.22.1
|
||||||
version: 0.22.1
|
version: 0.22.1
|
||||||
|
'@vercel/kv':
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
'@vercel/postgres':
|
'@vercel/postgres':
|
||||||
specifier: 0.7.2
|
specifier: 0.7.2
|
||||||
version: 0.7.2
|
version: 0.7.2
|
||||||
@ -3110,6 +3116,31 @@ packages:
|
|||||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@upstash/core-analytics@0.0.7:
|
||||||
|
resolution: {integrity: sha512-lC2j5efqb1haX/fpTGaPUx1rue1WUkOZBVHDzCB7eMIVsRdFFp4xiHtyH/G9omiR1zj39fU5SCTWFiKJH3KOpw==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
dependencies:
|
||||||
|
'@upstash/redis': 1.28.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@upstash/ratelimit@1.0.1:
|
||||||
|
resolution: {integrity: sha512-G9LZ7idhlkuYknbUngCB3qzd7QnkK1xDkFG5jRtEJZuOUS5UKJ0UTKbhalCtp39eX2wu2Ubv8W7HCeaJQOWM0A==}
|
||||||
|
dependencies:
|
||||||
|
'@upstash/core-analytics': 0.0.7
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@upstash/redis@1.25.1:
|
||||||
|
resolution: {integrity: sha512-ACj0GhJ4qrQyBshwFgPod6XufVEfKX2wcaihsEvSdLYnY+m+pa13kGt1RXm/yTHKf4TQi/Dy2A8z/y6WUEOmlg==}
|
||||||
|
dependencies:
|
||||||
|
crypto-js: 4.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@upstash/redis@1.28.4:
|
||||||
|
resolution: {integrity: sha512-UalkSAny/dz1m8giEhD3Y5ru1o+CPHI32wFyS3MyzDzj2TRvEN+lTw+mPwi20ojk0H2gs8TBW3qsrvwuLLy+pA==}
|
||||||
|
dependencies:
|
||||||
|
crypto-js: 4.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vercel/analytics@1.2.2(next@14.1.4)(react@18.2.0):
|
/@vercel/analytics@1.2.2(next@14.1.4)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==}
|
resolution: {integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -3136,6 +3167,13 @@ packages:
|
|||||||
undici: 5.28.3
|
undici: 5.28.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@vercel/kv@1.0.1:
|
||||||
|
resolution: {integrity: sha512-uTKddsqVYS2GRAM/QMNNXCTuw9N742mLoGRXoNDcyECaxEXvIHG0dEY+ZnYISV4Vz534VwJO+64fd9XeSggSKw==}
|
||||||
|
engines: {node: '>=14.6'}
|
||||||
|
dependencies:
|
||||||
|
'@upstash/redis': 1.25.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vercel/postgres@0.7.2:
|
/@vercel/postgres@0.7.2:
|
||||||
resolution: {integrity: sha512-IqR/ZAvoPGcPaXl9eWWB5KaA+w/81RzZa/18P4izQRHpNBkTGt9HwGfYi9+wut5UgxNq4QSX9A7HIQR6QDvX2Q==}
|
resolution: {integrity: sha512-IqR/ZAvoPGcPaXl9eWWB5KaA+w/81RzZa/18P4izQRHpNBkTGt9HwGfYi9+wut5UgxNq4QSX9A7HIQR6QDvX2Q==}
|
||||||
engines: {node: '>=14.6'}
|
engines: {node: '>=14.6'}
|
||||||
@ -3991,6 +4029,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/css-tree@2.3.1:
|
/css-tree@2.3.1:
|
||||||
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
|
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
|
||||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||||
|
|||||||
@ -2,13 +2,34 @@
|
|||||||
|
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
import { createStreamableValue, render } from 'ai/rsc';
|
import { createStreamableValue, render } from 'ai/rsc';
|
||||||
|
import { kv } from '@/services/vercel-kv';
|
||||||
|
import { Ratelimit } from '@upstash/ratelimit';
|
||||||
|
|
||||||
|
const RATE_LIMIT_IDENTIFIER = 'openai-image-query';
|
||||||
|
const RATE_LIMIT_MAX_QUERIES_PER_HOUR = 100;
|
||||||
|
|
||||||
const provider = new OpenAI({ apiKey: process.env.OPENAI_SECRET_KEY });
|
const provider = new OpenAI({ apiKey: process.env.OPENAI_SECRET_KEY });
|
||||||
|
|
||||||
|
// Allows 100 requests per hour
|
||||||
|
const ratelimit = kv
|
||||||
|
? new Ratelimit({
|
||||||
|
redis: kv,
|
||||||
|
limiter: Ratelimit.slidingWindow(RATE_LIMIT_MAX_QUERIES_PER_HOUR, '1h'),
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
export const streamOpenAiImageQuery = async (
|
export const streamOpenAiImageQuery = async (
|
||||||
imageBase64: string,
|
imageBase64: string,
|
||||||
query: string,
|
query: string,
|
||||||
) => {
|
) => {
|
||||||
|
if (ratelimit) {
|
||||||
|
const { success } = await ratelimit.limit(RATE_LIMIT_IDENTIFIER);
|
||||||
|
if (!success) {
|
||||||
|
console.error('OpenAI rate limit exceeded');
|
||||||
|
throw new Error('OpenAI rate limit exceeded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const stream = createStreamableValue('');
|
const stream = createStreamableValue('');
|
||||||
|
|
||||||
render({
|
render({
|
||||||
|
|||||||
10
src/services/vercel-kv.ts
Normal file
10
src/services/vercel-kv.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { createClient } from '@vercel/kv';
|
||||||
|
|
||||||
|
export const kv =
|
||||||
|
process.env.REDIS_REST_API_URL &&
|
||||||
|
process.env.REDIS_REST_API_TOKEN
|
||||||
|
? createClient({
|
||||||
|
url: process.env.REDIS_REST_API_URL,
|
||||||
|
token: process.env.REDIS_REST_API_TOKEN,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
@ -20,10 +20,12 @@ import { toastSuccess } from '@/toast';
|
|||||||
import { ConfigChecklistStatus } from './config';
|
import { ConfigChecklistStatus } from './config';
|
||||||
import StatusIcon from '@/components/StatusIcon';
|
import StatusIcon from '@/components/StatusIcon';
|
||||||
import { labelForStorage } from '@/services/storage';
|
import { labelForStorage } from '@/services/storage';
|
||||||
|
import { HiSparkles } from 'react-icons/hi';
|
||||||
|
|
||||||
export default function SiteChecklistClient({
|
export default function SiteChecklistClient({
|
||||||
hasPostgres,
|
hasVercelPostgres,
|
||||||
hasStorage,
|
hasVercelKV,
|
||||||
|
hasStorageProvider,
|
||||||
hasVercelBlobStorage,
|
hasVercelBlobStorage,
|
||||||
hasCloudflareR2Storage,
|
hasCloudflareR2Storage,
|
||||||
hasAwsS3Storage,
|
hasAwsS3Storage,
|
||||||
@ -140,7 +142,7 @@ export default function SiteChecklistClient({
|
|||||||
>
|
>
|
||||||
<ChecklistRow
|
<ChecklistRow
|
||||||
title="Setup database"
|
title="Setup database"
|
||||||
status={hasPostgres}
|
status={hasVercelPostgres}
|
||||||
isPending={isPendingPage}
|
isPending={isPendingPage}
|
||||||
>
|
>
|
||||||
{renderLink(
|
{renderLink(
|
||||||
@ -152,13 +154,13 @@ export default function SiteChecklistClient({
|
|||||||
and connect to project
|
and connect to project
|
||||||
</ChecklistRow>
|
</ChecklistRow>
|
||||||
<ChecklistRow
|
<ChecklistRow
|
||||||
title={!hasStorage
|
title={!hasStorageProvider
|
||||||
? 'Setup storage (one of the following)'
|
? 'Setup storage (one of the following)'
|
||||||
: hasMultipleStorageProviders
|
: hasMultipleStorageProviders
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
? `Setup storage (new uploads go to: ${labelForStorage(currentStorage)})`
|
? `Setup storage (new uploads go to: ${labelForStorage(currentStorage)})`
|
||||||
: 'Setup storage'}
|
: 'Setup storage'}
|
||||||
status={hasStorage}
|
status={hasStorageProvider}
|
||||||
isPending={isPendingPage}
|
isPending={isPendingPage}
|
||||||
>
|
>
|
||||||
{renderSubStatus(
|
{renderSubStatus(
|
||||||
@ -266,6 +268,38 @@ export default function SiteChecklistClient({
|
|||||||
{renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])}
|
{renderEnvVars(['NEXT_PUBLIC_SITE_DOMAIN'])}
|
||||||
</ChecklistRow>
|
</ChecklistRow>
|
||||||
</Checklist>
|
</Checklist>
|
||||||
|
<Checklist
|
||||||
|
title="AI Text Generation"
|
||||||
|
icon={<HiSparkles />}
|
||||||
|
optional
|
||||||
|
>
|
||||||
|
<ChecklistRow
|
||||||
|
title="Add OpenAI Secret Key"
|
||||||
|
status={isAiTextGenerationEnabled}
|
||||||
|
isPending={isPendingPage}
|
||||||
|
experimental
|
||||||
|
optional
|
||||||
|
>
|
||||||
|
Store your OpenAI secret key in order to add experimental support
|
||||||
|
for AI-generated text descriptions and enable an invisible field
|
||||||
|
called {'"Semantic Description"'} used to support CMD-K search
|
||||||
|
{renderEnvVars(['OPENAI_SECRET_KEY'])}
|
||||||
|
</ChecklistRow>
|
||||||
|
<ChecklistRow
|
||||||
|
title="Rate Limiting"
|
||||||
|
status={hasVercelKV}
|
||||||
|
isPending={isPendingPage}
|
||||||
|
optional
|
||||||
|
>
|
||||||
|
{renderLink(
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
'https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database',
|
||||||
|
'Create Vercel KV store',
|
||||||
|
)}
|
||||||
|
{' '}
|
||||||
|
and connect to project in order to enable rate limiting
|
||||||
|
</ChecklistRow>
|
||||||
|
</Checklist>
|
||||||
<Checklist
|
<Checklist
|
||||||
title="Settings"
|
title="Settings"
|
||||||
icon={<BiCog size={16} />}
|
icon={<BiCog size={16} />}
|
||||||
@ -301,18 +335,6 @@ export default function SiteChecklistClient({
|
|||||||
collection/display of location-based data
|
collection/display of location-based data
|
||||||
{renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])}
|
{renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])}
|
||||||
</ChecklistRow>
|
</ChecklistRow>
|
||||||
<ChecklistRow
|
|
||||||
title="AI-generated Text"
|
|
||||||
status={isAiTextGenerationEnabled}
|
|
||||||
isPending={isPendingPage}
|
|
||||||
experimental
|
|
||||||
optional
|
|
||||||
>
|
|
||||||
Store your OpenAI secret key in order to add experimental support
|
|
||||||
for AI-generated text descriptions and enable an invisible field
|
|
||||||
called {'"Semantic Description"'} used to support CMD-K search
|
|
||||||
{renderEnvVars(['OPENAI_SECRET_KEY'])}
|
|
||||||
</ChecklistRow>
|
|
||||||
<ChecklistRow
|
<ChecklistRow
|
||||||
title="Priority Order"
|
title="Priority Order"
|
||||||
status={isPriorityOrderEnabled}
|
status={isPriorityOrderEnabled}
|
||||||
|
|||||||
@ -37,6 +37,15 @@ export const SITE_DESCRIPTION =
|
|||||||
process.env.NEXT_PUBLIC_SITE_DESCRIPTION ||
|
process.env.NEXT_PUBLIC_SITE_DESCRIPTION ||
|
||||||
SITE_DOMAIN;
|
SITE_DOMAIN;
|
||||||
|
|
||||||
|
// STORAGE: VERCEL POSTGRES
|
||||||
|
export const HAS_VERCEL_POSTGRES =
|
||||||
|
(process.env.POSTGRES_HOST ?? '').length > 0;
|
||||||
|
|
||||||
|
// STORAGE: VERCEL KV
|
||||||
|
export const HAS_VERCEL_KV =
|
||||||
|
(process.env.REDIS_REST_API_URL ?? '').length > 0 &&
|
||||||
|
(process.env.REDIS_REST_API_TOKEN ?? '').length > 0;
|
||||||
|
|
||||||
// STORAGE: VERCEL BLOB
|
// STORAGE: VERCEL BLOB
|
||||||
export const HAS_VERCEL_BLOB_STORAGE =
|
export const HAS_VERCEL_BLOB_STORAGE =
|
||||||
(process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0;
|
(process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0;
|
||||||
@ -102,11 +111,12 @@ export const OG_TEXT_BOTTOM_ALIGNMENT =
|
|||||||
export const HIGH_DENSITY_GRID = GRID_ASPECT_RATIO <= 1;
|
export const HIGH_DENSITY_GRID = GRID_ASPECT_RATIO <= 1;
|
||||||
|
|
||||||
export const CONFIG_CHECKLIST_STATUS = {
|
export const CONFIG_CHECKLIST_STATUS = {
|
||||||
hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0,
|
hasVercelPostgres: HAS_VERCEL_POSTGRES,
|
||||||
|
hasVercelKV: HAS_VERCEL_KV,
|
||||||
hasVercelBlobStorage: HAS_VERCEL_BLOB_STORAGE,
|
hasVercelBlobStorage: HAS_VERCEL_BLOB_STORAGE,
|
||||||
hasCloudflareR2Storage: HAS_CLOUDFLARE_R2_STORAGE,
|
hasCloudflareR2Storage: HAS_CLOUDFLARE_R2_STORAGE,
|
||||||
hasAwsS3Storage: HAS_AWS_S3_STORAGE,
|
hasAwsS3Storage: HAS_AWS_S3_STORAGE,
|
||||||
hasStorage:
|
hasStorageProvider:
|
||||||
HAS_VERCEL_BLOB_STORAGE ||
|
HAS_VERCEL_BLOB_STORAGE ||
|
||||||
HAS_CLOUDFLARE_R2_STORAGE ||
|
HAS_CLOUDFLARE_R2_STORAGE ||
|
||||||
HAS_AWS_S3_STORAGE,
|
HAS_AWS_S3_STORAGE,
|
||||||
@ -135,7 +145,7 @@ export const CONFIG_CHECKLIST_STATUS = {
|
|||||||
export type ConfigChecklistStatus = typeof CONFIG_CHECKLIST_STATUS;
|
export type ConfigChecklistStatus = typeof CONFIG_CHECKLIST_STATUS;
|
||||||
|
|
||||||
export const IS_SITE_READY =
|
export const IS_SITE_READY =
|
||||||
CONFIG_CHECKLIST_STATUS.hasPostgres &&
|
CONFIG_CHECKLIST_STATUS.hasVercelPostgres &&
|
||||||
CONFIG_CHECKLIST_STATUS.hasStorage &&
|
CONFIG_CHECKLIST_STATUS.hasStorageProvider &&
|
||||||
CONFIG_CHECKLIST_STATUS.hasAuthSecret &&
|
CONFIG_CHECKLIST_STATUS.hasAuthSecret &&
|
||||||
CONFIG_CHECKLIST_STATUS.hasAdminUser;
|
CONFIG_CHECKLIST_STATUS.hasAdminUser;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user