Fix CORS configuration and add development tools

Torey Heinz committed Aug 24, 2025
commit 99c5f5ea6d0dcb42cc1770d0567f86a8361cf3f5
Showing 4 changed files with 157 additions and 7 deletions
CLAUDE.md +135 -0
@@ @@ -0,0 +1,135 @@
+ # CLAUDE.md
+
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+ ## Project Overview
+
+ QRurl is a serverless URL shortener with QR code generation built as a monorepo with:
+ - **Backend**: Cloudflare Workers (src/) with D1 database, R2 storage, and KV caching
+ - **Frontend**: Vue 3 SPA (frontend/) with Tailwind CSS, Pinia, and Vue Router
+
+ ## Development Commands
+
+ ### Backend (Cloudflare Workers)
+ ```bash
+ # Start backend dev server (port 8787)
+ npm run dev
+
+ # Initialize local D1 database
+ npm run db:init
+
+ # Initialize remote D1 database
+ npm run db:init:remote
+
+ # Deploy to Cloudflare Workers
+ npm run deploy
+ ```
+
+ ### Frontend (Vue.js)
+ ```bash
+ cd frontend
+
+ # Start frontend dev server (port 3000)
+ npm run dev
+
+ # Build for production
+ npm run build
+
+ # Preview production build
+ npm run preview
+ ```
+
+ ### Both Services Running
+
+ #### Option 1: Using Foreman
+ ```bash
+ # Install foreman if not already installed
+ gem install foreman
+
+ # Start both services
+ foreman start
+ ```
+
+ #### Option 2: Manual
+ Start backend first, then frontend in separate terminals. Frontend proxies API calls to backend via Vite config.
+
+ ## Architecture & Key Design Patterns
+
+ ### Backend Architecture
+ - **Entry Point**: `src/index.js` - Main router handling all requests
+ - **Custom Router**: `src/router.js` - Lightweight routing implementation
+ - **Middleware Pipeline**: Applied in order - CORS → Rate Limiting → Auth verification
+ - **Database Layer**: `src/lib/db.js` - All D1 operations centralized here
+ - **Auth Flow**: Magic links with JWT tokens, stored in `auth_tokens` table with 15-min expiry
+
+ ### Frontend Architecture
+ - **State Management**: Pinia stores (`authStore`, `entriesStore`) handle all API calls
+ - **API Service**: `src/services/api.js` with axios interceptors for auth
+ - **QR Generation**: `src/services/qrcode.js` generates QR with optional logo overlay (30% max)
+ - **Auth Guard**: Router middleware redirects unauthenticated users to login
+
+ ### CORS Configuration
+ - Backend middleware in `src/middleware/cors.js` handles preflight
+ - Frontend Vite proxy in development
+ - Production requires proper FRONTEND_URL in environment variables
+
+ ## Cloudflare Resources
+
+ All resources use `qrurl-` prefix:
+ - **D1 Database**: `qrurl-db` (ID: 17eb6fdb-19da-4ed7-931c-a4cdef281f8c)
+ - **R2 Bucket**: `qrurl-storage`
+ - **KV Namespace**: `qrurl-cache` (ID: 1cacb0f1b44b4324b62c1bc010ff15f5)
+
+ ## Environment Configuration
+
+ ### Backend (.dev.vars)
+ ```
+ JWT_SECRET=dev-secret-change-in-production
+ EMAIL_API_KEY=your-resend-or-sendgrid-api-key
+ AUTHORIZED_EMAILS=test@example.com,admin@example.com
+ ```
+
+ ### Frontend (frontend/.env)
+ ```
+ VITE_API_URL=http://localhost:8787/api
+ VITE_SHORT_URL=http://localhost:8787
+ ```
+
+ ## Database Schema
+
+ Key tables and their relationships:
+ - `users` → `entries` (one-to-many)
+ - `entries` → `analytics` (one-to-many)
+ - `auth_tokens` - temporary magic link tokens
+ - `authorized_emails` - email whitelist
+
+ All tables have proper indexes for performance-critical queries (slug lookups, analytics aggregation).
+
+ ## Common Issues & Solutions
+
+ ### CORS Errors
+ - Check `FRONTEND_URL` in backend environment variables
+ - Ensure middleware order in `src/middleware/index.js` is correct
+ - Verify OPTIONS preflight handling in `src/middleware/cors.js`
+
+ ### Tailwind CSS v4 Issues
+ Project uses Tailwind CSS v3 (not v4) due to PostCSS compatibility. Don't upgrade without updating PostCSS config.
+
+ ### Auth Flow
+ 1. Email must be in `authorized_emails` table or `AUTHORIZED_EMAILS` env var
+ 2. Magic link tokens expire in 15 minutes
+ 3. JWT tokens stored in localStorage, expire in 7 days
+ 4. Public endpoints skip auth: `/health`, `/api/auth/*`, redirect paths
+
+ ## API Response Patterns
+
+ All API responses follow:
+ ```javascript
+ // Success
+ { success: true, data: {...} }
+
+ // Error
+ { error: "Error message" }
+ ```
+
+ Protected routes require `Authorization: Bearer <token>` header.
\ No newline at end of file
Procfile +2 -0
@@ @@ -0,0 +1,2 @@
+ backend: npm run dev
+ frontend: cd frontend && npm run dev
\ No newline at end of file
src/index.js +20 -2
@@ @@ -4,10 +4,24 @@ import { authRoutes } from './routes/auth';
import { apiRoutes } from './routes/api';
import { applyMiddleware } from './middleware';
import { handleError } from './utils/errors';
+ import { addCorsHeaders } from './middleware/cors';
export default {
async fetch(request, env, ctx) {
try {
+ // Handle OPTIONS preflight requests
+ if (request.method === 'OPTIONS') {
+ return new Response(null, {
+ status: 204,
+ headers: {
+ 'Access-Control-Allow-Origin': env.FRONTEND_URL || '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
+ 'Access-Control-Max-Age': '86400',
+ }
+ });
+ }
+
const router = new Router();
// Health check
@@ @@ -41,9 +55,13 @@ export default {
});
});
- return await router.handle(request, env, ctx);
+ const response = await router.handle(request, env, ctx);
+
+ // Add CORS headers to all responses
+ return addCorsHeaders(response, env);
} catch (error) {
- return handleError(error);
+ const errorResponse = handleError(error);
+ return addCorsHeaders(errorResponse, env);
}
}
};
\ No newline at end of file
src/middleware/index.js +0 -5
@@ @@ -1,12 +1,7 @@
import { verifyAuth } from './auth';
import { rateLimit } from './rateLimit';
- import { cors } from './cors';
export async function applyMiddleware(request, env, ctx, handler) {
- // Apply CORS
- const corsResponse = cors(request, env);
- if (corsResponse) return corsResponse;
-
// Apply rate limiting
const rateLimitResponse = await rateLimit(request, env);
if (rateLimitResponse) return rateLimitResponse;