From 654a3cf84c19f887e12e8e94e52896d28f2d2cb1 Mon Sep 17 00:00:00 2001 From: Leon van Zyl Date: Sun, 30 Nov 2025 08:16:24 +0200 Subject: [PATCH] add file storage, local and prod --- .gitignore | 3 + CLAUDE.md | 24 ++++- README.md | 27 +++++ create-agentic-app/package-lock.json | 4 +- create-agentic-app/package.json | 2 +- create-agentic-app/template/CLAUDE.md | 24 ++++- create-agentic-app/template/README.md | 27 +++++ create-agentic-app/template/_gitignore | 3 + create-agentic-app/template/package.json | 1 + .../template/src/app/api/diagnostics/route.ts | 12 +++ create-agentic-app/template/src/app/page.tsx | 2 +- .../src/components/setup-checklist.tsx | 14 +++ .../template/src/lib/storage.ts | 102 ++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 48 +++++++++ src/app/api/diagnostics/route.ts | 12 +++ src/app/page.tsx | 2 +- src/components/setup-checklist.tsx | 14 +++ src/lib/storage.ts | 102 ++++++++++++++++++ 19 files changed, 415 insertions(+), 9 deletions(-) create mode 100644 create-agentic-app/template/src/lib/storage.ts create mode 100644 src/lib/storage.ts diff --git a/.gitignore b/.gitignore index 10c46df..32c8783 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# uploads +public/uploads \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index d2360bc..eabdb4e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,6 +68,7 @@ src/ ├── auth-client.ts # Better Auth client hooks ├── db.ts # Database connection ├── schema.ts # Drizzle schema (users, sessions, etc.) + ├── storage.ts # File storage abstraction (Vercel Blob / local) └── utils.ts # Utility functions (cn, etc.) ``` @@ -92,6 +93,9 @@ OPENROUTER_MODEL=openai/gpt-5-mini # or any model from openrouter.ai/models # App NEXT_PUBLIC_APP_URL=http://localhost:3000 + +# File Storage (optional) +BLOB_READ_WRITE_TOKEN= # Leave empty for local dev, set for Vercel Blob in production ``` ## Available Scripts @@ -162,14 +166,22 @@ The project includes technical documentation in `docs/`: - Always run migrations after schema changes - PostgreSQL is the database (not SQLite, MySQL, etc.) -7. **Component Creation** +7. **File Storage** + + - Use the storage abstraction from `@/lib/storage` + - Automatically uses local storage (dev) or Vercel Blob (production) + - Import: `import { upload, deleteFile } from "@/lib/storage"` + - Example: `const result = await upload(buffer, "avatar.png", "avatars")` + - Storage switches based on `BLOB_READ_WRITE_TOKEN` environment variable + +8. **Component Creation** - Use existing shadcn/ui components when possible - Follow the established patterns in `src/components/ui/` - Support both light and dark modes - Use TypeScript with proper types -8. **API Routes** +9. **API Routes** - Follow Next.js 15 App Router conventions - Use Route Handlers (route.ts files) - Return Response objects @@ -217,6 +229,14 @@ The project includes technical documentation in `docs/`: 3. Reference streaming docs: `docs/technical/ai/streaming.md` 4. Remember to use OpenRouter, not direct OpenAI +**Working with file storage:** + +1. Import storage functions: `import { upload, deleteFile } from "@/lib/storage"` +2. Upload files: `const result = await upload(fileBuffer, "filename.png", "folder")` +3. Delete files: `await deleteFile(result.url)` +4. Storage automatically uses local filesystem in dev, Vercel Blob in production +5. Local files are saved to `public/uploads/` and served at `/uploads/` + ## Package Manager This project uses **pnpm** (see `pnpm-lock.yaml`). When running commands: diff --git a/README.md b/README.md index 9aef610..c72c33c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A complete agentic coding boilerplate with authentication, PostgreSQL database, - **🔐 Authentication**: Better Auth with Google OAuth integration - **🗃️ Database**: Drizzle ORM with PostgreSQL - **🤖 AI Integration**: Vercel AI SDK with OpenRouter (access to 100+ AI models) +- **📁 File Storage**: Automatic local/Vercel Blob storage with seamless switching - **🎨 UI Components**: shadcn/ui with Tailwind CSS - **⚡ Modern Stack**: Next.js 15, React 19, TypeScript - **📱 Responsive**: Mobile-first design approach @@ -113,6 +114,11 @@ OPENROUTER_MODEL="openai/gpt-5-mini" # App URL (for production deployments) NEXT_PUBLIC_APP_URL="http://localhost:3000" + +# File Storage (Optional - for file upload functionality) +# Leave empty to use local storage (public/uploads/) in development +# Set to enable Vercel Blob storage in production +BLOB_READ_WRITE_TOKEN="" ``` **4. Database Setup** @@ -163,6 +169,25 @@ Your application will be available at [http://localhost:3000](http://localhost:3 5. Copy the API key and add it to your `.env` file as `OPENROUTER_API_KEY` 6. Browse available models at OpenRouter Models +### File Storage Configuration + +The project includes a flexible storage abstraction that automatically switches between local filesystem storage (development) and Vercel Blob storage (production). + +**For Development (Local Storage):** +- Leave `BLOB_READ_WRITE_TOKEN` empty or unset in your `.env` file +- Files are automatically stored in `public/uploads/` +- Files are served at `/uploads/` URL path +- No external service or configuration needed + +**For Production (Vercel Blob):** +1. Go to Vercel Dashboard +2. Navigate to your project → **Storage** tab +3. Click **Create** → **Blob** +4. Copy the `BLOB_READ_WRITE_TOKEN` from the integration +5. Add it to your production environment variables + +The storage service automatically detects which backend to use based on the presence of the `BLOB_READ_WRITE_TOKEN` environment variable. + ## 🗂️ Project Structure ``` @@ -182,6 +207,7 @@ src/ ├── auth-client.ts # Client-side auth utilities ├── db.ts # Database connection ├── schema.ts # Database schema + ├── storage.ts # File storage abstraction └── utils.ts # General utilities ``` @@ -236,6 +262,7 @@ Ensure these are set in your production environment: - `OPENROUTER_API_KEY` - OpenRouter API key (optional, for AI chat functionality) - `OPENROUTER_MODEL` - Model name from OpenRouter (optional, defaults to openai/gpt-5-mini) - `NEXT_PUBLIC_APP_URL` - Your production domain +- `BLOB_READ_WRITE_TOKEN` - Vercel Blob token (optional, uses local storage if not set) ## 🎥 Tutorial Video diff --git a/create-agentic-app/package-lock.json b/create-agentic-app/package-lock.json index 4ac1e61..9057735 100644 --- a/create-agentic-app/package-lock.json +++ b/create-agentic-app/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-agentic-app", - "version": "1.1.17", + "version": "1.1.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-agentic-app", - "version": "1.1.17", + "version": "1.1.18", "license": "MIT", "dependencies": { "chalk": "^5.3.0", diff --git a/create-agentic-app/package.json b/create-agentic-app/package.json index bb05e98..a458c1b 100644 --- a/create-agentic-app/package.json +++ b/create-agentic-app/package.json @@ -1,6 +1,6 @@ { "name": "create-agentic-app", - "version": "1.1.17", + "version": "1.1.18", "description": "Scaffold a new agentic AI application with Next.js, Better Auth, and AI SDK", "type": "module", "bin": { diff --git a/create-agentic-app/template/CLAUDE.md b/create-agentic-app/template/CLAUDE.md index d2360bc..eabdb4e 100644 --- a/create-agentic-app/template/CLAUDE.md +++ b/create-agentic-app/template/CLAUDE.md @@ -68,6 +68,7 @@ src/ ├── auth-client.ts # Better Auth client hooks ├── db.ts # Database connection ├── schema.ts # Drizzle schema (users, sessions, etc.) + ├── storage.ts # File storage abstraction (Vercel Blob / local) └── utils.ts # Utility functions (cn, etc.) ``` @@ -92,6 +93,9 @@ OPENROUTER_MODEL=openai/gpt-5-mini # or any model from openrouter.ai/models # App NEXT_PUBLIC_APP_URL=http://localhost:3000 + +# File Storage (optional) +BLOB_READ_WRITE_TOKEN= # Leave empty for local dev, set for Vercel Blob in production ``` ## Available Scripts @@ -162,14 +166,22 @@ The project includes technical documentation in `docs/`: - Always run migrations after schema changes - PostgreSQL is the database (not SQLite, MySQL, etc.) -7. **Component Creation** +7. **File Storage** + + - Use the storage abstraction from `@/lib/storage` + - Automatically uses local storage (dev) or Vercel Blob (production) + - Import: `import { upload, deleteFile } from "@/lib/storage"` + - Example: `const result = await upload(buffer, "avatar.png", "avatars")` + - Storage switches based on `BLOB_READ_WRITE_TOKEN` environment variable + +8. **Component Creation** - Use existing shadcn/ui components when possible - Follow the established patterns in `src/components/ui/` - Support both light and dark modes - Use TypeScript with proper types -8. **API Routes** +9. **API Routes** - Follow Next.js 15 App Router conventions - Use Route Handlers (route.ts files) - Return Response objects @@ -217,6 +229,14 @@ The project includes technical documentation in `docs/`: 3. Reference streaming docs: `docs/technical/ai/streaming.md` 4. Remember to use OpenRouter, not direct OpenAI +**Working with file storage:** + +1. Import storage functions: `import { upload, deleteFile } from "@/lib/storage"` +2. Upload files: `const result = await upload(fileBuffer, "filename.png", "folder")` +3. Delete files: `await deleteFile(result.url)` +4. Storage automatically uses local filesystem in dev, Vercel Blob in production +5. Local files are saved to `public/uploads/` and served at `/uploads/` + ## Package Manager This project uses **pnpm** (see `pnpm-lock.yaml`). When running commands: diff --git a/create-agentic-app/template/README.md b/create-agentic-app/template/README.md index 9aef610..c72c33c 100644 --- a/create-agentic-app/template/README.md +++ b/create-agentic-app/template/README.md @@ -7,6 +7,7 @@ A complete agentic coding boilerplate with authentication, PostgreSQL database, - **🔐 Authentication**: Better Auth with Google OAuth integration - **🗃️ Database**: Drizzle ORM with PostgreSQL - **🤖 AI Integration**: Vercel AI SDK with OpenRouter (access to 100+ AI models) +- **📁 File Storage**: Automatic local/Vercel Blob storage with seamless switching - **🎨 UI Components**: shadcn/ui with Tailwind CSS - **⚡ Modern Stack**: Next.js 15, React 19, TypeScript - **📱 Responsive**: Mobile-first design approach @@ -113,6 +114,11 @@ OPENROUTER_MODEL="openai/gpt-5-mini" # App URL (for production deployments) NEXT_PUBLIC_APP_URL="http://localhost:3000" + +# File Storage (Optional - for file upload functionality) +# Leave empty to use local storage (public/uploads/) in development +# Set to enable Vercel Blob storage in production +BLOB_READ_WRITE_TOKEN="" ``` **4. Database Setup** @@ -163,6 +169,25 @@ Your application will be available at [http://localhost:3000](http://localhost:3 5. Copy the API key and add it to your `.env` file as `OPENROUTER_API_KEY` 6. Browse available models at OpenRouter Models +### File Storage Configuration + +The project includes a flexible storage abstraction that automatically switches between local filesystem storage (development) and Vercel Blob storage (production). + +**For Development (Local Storage):** +- Leave `BLOB_READ_WRITE_TOKEN` empty or unset in your `.env` file +- Files are automatically stored in `public/uploads/` +- Files are served at `/uploads/` URL path +- No external service or configuration needed + +**For Production (Vercel Blob):** +1. Go to Vercel Dashboard +2. Navigate to your project → **Storage** tab +3. Click **Create** → **Blob** +4. Copy the `BLOB_READ_WRITE_TOKEN` from the integration +5. Add it to your production environment variables + +The storage service automatically detects which backend to use based on the presence of the `BLOB_READ_WRITE_TOKEN` environment variable. + ## 🗂️ Project Structure ``` @@ -182,6 +207,7 @@ src/ ├── auth-client.ts # Client-side auth utilities ├── db.ts # Database connection ├── schema.ts # Database schema + ├── storage.ts # File storage abstraction └── utils.ts # General utilities ``` @@ -236,6 +262,7 @@ Ensure these are set in your production environment: - `OPENROUTER_API_KEY` - OpenRouter API key (optional, for AI chat functionality) - `OPENROUTER_MODEL` - Model name from OpenRouter (optional, defaults to openai/gpt-5-mini) - `NEXT_PUBLIC_APP_URL` - Your production domain +- `BLOB_READ_WRITE_TOKEN` - Vercel Blob token (optional, uses local storage if not set) ## 🎥 Tutorial Video diff --git a/create-agentic-app/template/_gitignore b/create-agentic-app/template/_gitignore index 10c46df..32c8783 100644 --- a/create-agentic-app/template/_gitignore +++ b/create-agentic-app/template/_gitignore @@ -41,3 +41,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# uploads +public/uploads \ No newline at end of file diff --git a/create-agentic-app/template/package.json b/create-agentic-app/template/package.json index 4bbfb5f..963b74e 100644 --- a/create-agentic-app/template/package.json +++ b/create-agentic-app/template/package.json @@ -22,6 +22,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-slot": "^1.2.3", + "@vercel/blob": "^2.0.0", "ai": "^5.0.86", "better-auth": "^1.3.34", "class-variance-authority": "^0.7.1", diff --git a/create-agentic-app/template/src/app/api/diagnostics/route.ts b/create-agentic-app/template/src/app/api/diagnostics/route.ts index 183194b..f5eabae 100644 --- a/create-agentic-app/template/src/app/api/diagnostics/route.ts +++ b/create-agentic-app/template/src/app/api/diagnostics/route.ts @@ -24,6 +24,10 @@ interface DiagnosticsResponse { ai: { configured: boolean; }; + storage: { + configured: boolean; + type: "local" | "remote"; + }; overallStatus: StatusLevel; } @@ -115,6 +119,10 @@ export async function GET(req: Request) { env.BETTER_AUTH_SECRET && env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET; const aiConfigured = env.OPENROUTER_API_KEY; // We avoid live-calling the AI provider here + // Storage configuration check + const storageConfigured = Boolean(process.env.BLOB_READ_WRITE_TOKEN); + const storageType: "local" | "remote" = storageConfigured ? "remote" : "local"; + const overallStatus: StatusLevel = (() => { if (!env.POSTGRES_URL || !dbConnected || !schemaApplied) return "error"; if (!authConfigured) return "error"; @@ -138,6 +146,10 @@ export async function GET(req: Request) { ai: { configured: aiConfigured, }, + storage: { + configured: storageConfigured, + type: storageType, + }, overallStatus, }; diff --git a/create-agentic-app/template/src/app/page.tsx b/create-agentic-app/template/src/app/page.tsx index 6110fd3..7583baa 100644 --- a/create-agentic-app/template/src/app/page.tsx +++ b/create-agentic-app/template/src/app/page.tsx @@ -43,7 +43,7 @@ export default function Home() {