Generated by Spark: Should handle caprover / cloudflare cors - Check frontend and backend config. example setup: https://frontend.example.com https://backend.example.com

This commit is contained in:
2026-01-17 18:47:08 +00:00
committed by GitHub
parent ca93235cde
commit a469b44a54
17 changed files with 2165 additions and 200 deletions

View File

@@ -1,4 +1,16 @@
# Flask Backend Configuration (Optional) # Frontend Configuration
# If set, the app will automatically use Flask backend instead of IndexedDB # Flask Backend URL - If set, the app will automatically use Flask backend instead of IndexedDB
# Example: VITE_FLASK_BACKEND_URL=http://localhost:5000 # Development: VITE_FLASK_BACKEND_URL=http://localhost:5000
# Production: VITE_FLASK_BACKEND_URL=https://backend.example.com
VITE_FLASK_BACKEND_URL= VITE_FLASK_BACKEND_URL=
# Backend Configuration (for backend/app.py)
# CORS Allowed Origins - Comma-separated list of allowed frontend URLs
# Development: CORS_ALLOWED_ORIGINS=*
# Production: CORS_ALLOWED_ORIGINS=https://frontend.example.com
# Multiple: CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com
CORS_ALLOWED_ORIGINS=*
# Database Path - Location of SQLite database file in backend
# Default: DATABASE_PATH=/app/data/snippets.db
DATABASE_PATH=/app/data/snippets.db

View File

@@ -148,7 +148,9 @@ Or configure manually in Settings with the remote URL.
| Variable | Description | Default | Example | | Variable | Description | Default | Example |
|----------|-------------|---------|---------| |----------|-------------|---------|---------|
| `VITE_FLASK_BACKEND_URL` | Flask backend URL. When set, forces Flask backend usage. | (none) | `http://localhost:5000` | | `VITE_FLASK_BACKEND_URL` | Flask backend URL. When set, forces Flask backend usage. | (none) | `http://localhost:5000` or `https://backend.example.com` |
| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed frontend origins for CORS. | `*` | `https://frontend.example.com` |
| `DATABASE_PATH` | Path to SQLite database file in backend. | `/app/data/snippets.db` | `/app/data/snippets.db` |
## Troubleshooting ## Troubleshooting
@@ -164,7 +166,8 @@ Or configure manually in Settings with the remote URL.
1. Verify backend is running: `curl http://localhost:5000/health` 1. Verify backend is running: `curl http://localhost:5000/health`
2. Check URL spelling and port number 2. Check URL spelling and port number
3. Review backend logs for errors 3. Review backend logs for errors
4. Ensure CORS is enabled in Flask app 4. Ensure CORS is enabled in Flask app (see CORS-GUIDE.md)
5. For production deployments, see DEPLOYMENT.md
### Environment variable not working ### Environment variable not working
@@ -258,3 +261,6 @@ VITE_FLASK_BACKEND_URL=https://api.your-domain.com
- [Backend API Documentation](./backend/README.md#api-endpoints) - [Backend API Documentation](./backend/README.md#api-endpoints)
- [Docker Compose Configuration](./docker-compose.yml) - [Docker Compose Configuration](./docker-compose.yml)
- [Example .env file](./.env.example) - [Example .env file](./.env.example)
- [CORS Configuration Guide](./CORS-GUIDE.md)
- [Production Deployment Guide](./DEPLOYMENT.md)
- [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md)

383
CORS-CONFIG-SUMMARY.md Normal file
View File

@@ -0,0 +1,383 @@
# CapRover/Cloudflare CORS Configuration Summary
## ✅ What's Been Configured
### Backend CORS Implementation
The Flask backend (`backend/app.py`) now includes comprehensive CORS support:
1. **Environment-based CORS configuration:**
- `CORS_ALLOWED_ORIGINS` environment variable
- Supports wildcard (`*`) for development
- Supports comma-separated list for multiple origins in production
2. **Proper CORS headers:**
- `Access-Control-Allow-Origin`
- `Access-Control-Allow-Methods`
- `Access-Control-Allow-Headers`
- `Access-Control-Allow-Credentials`
3. **Security features:**
- Specific origins in production
- Wildcard only in development
- Credentials support for specific origins
### Frontend Configuration
The frontend automatically detects and uses the backend via:
1. **Environment variable:** `VITE_FLASK_BACKEND_URL`
2. **Automatic configuration:** When set, forces Flask backend usage
3. **Manual configuration:** Settings page (if env var not set)
### Docker Configuration
1. **Backend Dockerfile:**
- Environment variables support
- Persistent volume at `/app/data`
- Health check endpoint
2. **Frontend Dockerfile:**
- Build-time argument for backend URL
- Nginx with proxy support
- Static file serving
3. **Nginx Configuration:**
- Proper proxy headers
- Cache control for SPA
- API proxying
### CapRover Support
1. **captain-definition files:**
- Frontend: Root directory
- Backend: Backend directory
2. **Deployment ready:**
- Separate app deployments
- Environment variable configuration
- Persistent storage support
## 📚 Documentation Created
### Primary Guides
1. **[DEPLOYMENT.md](./DEPLOYMENT.md)**
- Complete CapRover/Cloudflare deployment walkthrough
- Step-by-step instructions
- DNS configuration
- SSL setup
- Testing procedures
- Troubleshooting
2. **[CORS-GUIDE.md](./CORS-GUIDE.md)**
- CORS concepts and configuration
- Testing procedures
- Common errors and solutions
- Automated testing script
- Security best practices
- Debugging tips
3. **[DEPLOYMENT-CHECKLIST.md](./DEPLOYMENT-CHECKLIST.md)**
- Quick reference checklist
- All deployment steps
- Testing verification
- Security checks
- Quick commands
4. **[ENV-CONFIG.md](./ENV-CONFIG.md)**
- Environment variable examples
- Different deployment scenarios
- Common mistakes
- Troubleshooting
### Updated Documentation
1. **[BACKEND-CONFIG.md](./BACKEND-CONFIG.md)**
- Added CORS environment variable
- Updated with deployment links
2. **[backend/README.md](./backend/README.md)**
- Added CORS configuration
- Production deployment section
- Environment variables table
3. **[README.md](./README.md)**
- Added deployment documentation links
- Organized documentation section
### Configuration Files
1. **[.env.example](./.env.example)**
- Frontend variables
- Backend variables
- Comments and examples
2. **[docker-compose.yml](./docker-compose.yml)**
- Updated with new environment variables
- Proper volume paths
3. **[docker-compose.production.yml](./docker-compose.production.yml)**
- Production configuration example
- Network configuration
4. **[nginx.conf](./nginx.conf)**
- Enhanced proxy configuration
- Security headers
- Cache control
### Testing Tools
1. **[test-cors.sh](./test-cors.sh)**
- Automated CORS testing script
- 5 comprehensive tests
- Clear pass/fail indicators
- Usage instructions
## 🚀 Quick Start Guide
### Local Development
```bash
# Backend
cd backend
pip install -r requirements.txt
CORS_ALLOWED_ORIGINS=http://localhost:3000 python app.py
# Frontend
echo "VITE_FLASK_BACKEND_URL=http://localhost:5000" > .env
npm install
npm run dev
```
### Docker Compose
```bash
# Starts both frontend and backend
docker-compose up -d
# Access at http://localhost:3000
```
### CapRover Deployment
```bash
# Deploy backend
cd backend
caprover deploy -a codesnippet-backend
# Configure in CapRover dashboard:
# - CORS_ALLOWED_ORIGINS=https://frontend.example.com
# - DATABASE_PATH=/app/data/snippets.db
# Deploy frontend
cd ..
caprover deploy -a codesnippet-frontend
# Configure in CapRover dashboard:
# - VITE_FLASK_BACKEND_URL=https://backend.example.com
```
## 🔧 Configuration Examples
### Separate Domains (Recommended)
```
Frontend: https://frontend.example.com
Backend: https://backend.example.com
```
**Frontend:**
```bash
VITE_FLASK_BACKEND_URL=https://backend.example.com
```
**Backend:**
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com
DATABASE_PATH=/app/data/snippets.db
```
### Single Domain (Proxied)
```
Frontend: https://app.example.com
Backend: https://app.example.com/api (proxied)
```
**Frontend:**
```bash
VITE_FLASK_BACKEND_URL=/api
```
**Backend:**
```bash
CORS_ALLOWED_ORIGINS=*
DATABASE_PATH=/app/data/snippets.db
```
### Multiple Frontends
```
Frontend 1: https://app.example.com
Frontend 2: https://staging.example.com
Backend: https://api.example.com
```
**Backend:**
```bash
CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com
DATABASE_PATH=/app/data/snippets.db
```
## ✅ Testing CORS
### Quick Test
```bash
curl -H "Origin: https://frontend.example.com" \
-H "Access-Control-Request-Method: GET" \
-X OPTIONS \
https://backend.example.com/api/snippets
```
Should return:
```
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```
### Automated Test
```bash
chmod +x test-cors.sh
./test-cors.sh https://backend.example.com https://frontend.example.com
```
## 🔒 Security Checklist
- [ ] HTTPS enabled for both frontend and backend
- [ ] `CORS_ALLOWED_ORIGINS` set to specific domains (not `*`)
- [ ] Cloudflare proxy enabled (orange cloud)
- [ ] SSL/TLS mode set to "Full (strict)"
- [ ] Flask debug mode disabled
- [ ] Database backed up regularly
- [ ] Rate limiting configured
- [ ] Monitoring enabled
## 📋 Environment Variables
### Frontend
| Variable | Required | Example |
|----------|----------|---------|
| `VITE_FLASK_BACKEND_URL` | Yes (for Flask) | `https://backend.example.com` |
### Backend
| Variable | Required | Example |
|----------|----------|---------|
| `CORS_ALLOWED_ORIGINS` | Yes | `https://frontend.example.com` |
| `DATABASE_PATH` | No | `/app/data/snippets.db` (default) |
## 🐛 Troubleshooting
### CORS Errors
**Problem:** Browser shows CORS policy error
**Solution:**
1. Check `CORS_ALLOWED_ORIGINS` includes frontend URL
2. Verify both use HTTPS (not mixed)
3. Restart backend after env changes
4. See [CORS-GUIDE.md](./CORS-GUIDE.md)
### Connection Failed
**Problem:** Frontend can't connect to backend
**Solution:**
1. Test backend: `curl https://backend.example.com/health`
2. Check `VITE_FLASK_BACKEND_URL` is correct
3. Verify backend is running
4. Check firewall/network settings
### SSL Issues
**Problem:** SSL certificate not valid
**Solution:**
1. Wait 5-10 minutes for Let's Encrypt
2. Verify DNS records are correct
3. Check Cloudflare SSL mode: "Full (strict)"
4. Disable and re-enable HTTPS in CapRover
## 📖 Documentation Index
### For Developers
- [Backend Configuration](./BACKEND-CONFIG.md) - Configure storage backends
- [Environment Configuration](./ENV-CONFIG.md) - Environment variable examples
- [Backend API](./backend/README.md) - API documentation
### For DevOps
- [Deployment Guide](./DEPLOYMENT.md) - Complete deployment walkthrough
- [CORS Guide](./CORS-GUIDE.md) - CORS configuration and testing
- [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md) - Quick reference
### For Everyone
- [Quick Start](./QUICKSTART.md) - Get started quickly
- [Application Guide](./README-APP.md) - Using the application
- [Main README](./README.md) - Overview and links
## 🎯 Key Benefits
1. **Flexible Deployment:**
- Single domain or separate domains
- CapRover, Docker, or standalone
- Local development or cloud production
2. **Secure by Default:**
- CORS properly configured
- HTTPS enforced
- Specific origins in production
3. **Easy to Configure:**
- Environment variables
- Clear documentation
- Testing tools included
4. **Production Ready:**
- Cloudflare CDN support
- CapRover deployment ready
- Monitoring and logging
5. **Well Documented:**
- Step-by-step guides
- Configuration examples
- Troubleshooting help
## 🤝 Support
For issues or questions:
1. Check the relevant guide in [Documentation Index](#documentation-index)
2. Review [Troubleshooting](#troubleshooting) section
3. Run the test script: `./test-cors.sh`
4. Check application logs
## 📝 Next Steps
After deployment:
1. ✅ Test all functionality
2. ✅ Configure backups
3. ✅ Set up monitoring
4. ✅ Review security settings
5. ✅ Configure rate limiting
6. ✅ Test disaster recovery
7. ✅ Document your specific configuration
8. ✅ Share with your team
---
**Ready to deploy?** Start with the [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md)!

409
CORS-GUIDE.md Normal file
View File

@@ -0,0 +1,409 @@
# CORS Configuration & Testing Guide
This guide covers Cross-Origin Resource Sharing (CORS) configuration for CodeSnippet when deployed with separate frontend and backend domains.
## Understanding CORS
CORS is a security feature that controls which domains can make requests to your backend API. When your frontend (`https://frontend.example.com`) makes requests to your backend (`https://backend.example.com`), the browser enforces CORS policies.
## Backend CORS Configuration
### Environment Variable: `CORS_ALLOWED_ORIGINS`
The backend Flask application uses the `CORS_ALLOWED_ORIGINS` environment variable to control which origins can access the API.
#### Development (Allow All Origins)
```bash
CORS_ALLOWED_ORIGINS=*
```
**Warning:** Only use `*` in development. This allows ANY website to access your backend API.
#### Production (Specific Origins)
```bash
# Single origin
CORS_ALLOWED_ORIGINS=https://frontend.example.com
# Multiple origins (comma-separated)
CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com,https://staging.example.com
```
**Important:** Do NOT include trailing slashes in URLs.
### How It Works
The Flask backend (`backend/app.py`) reads this environment variable and configures CORS accordingly:
```python
ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', '*')
if ALLOWED_ORIGINS == '*':
CORS(app, origins='*', ...) # Development mode
else:
origins_list = [origin.strip() for origin in ALLOWED_ORIGINS.split(',')]
CORS(app, origins=origins_list, ...) # Production mode
```
### CORS Headers Returned
When properly configured, the backend returns these headers:
```
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
```
## Testing CORS Configuration
### Test 1: Health Check (No CORS)
Simple GET request to verify backend is running:
```bash
curl https://backend.example.com/health
```
Expected response:
```json
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00"
}
```
### Test 2: Preflight Request (OPTIONS)
Browsers send an OPTIONS request before the actual request to check CORS permissions:
```bash
curl -X OPTIONS https://backend.example.com/api/snippets \
-H "Origin: https://frontend.example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Content-Type" \
-v
```
Expected headers in response:
```
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: https://frontend.example.com
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
< Access-Control-Allow-Headers: Content-Type, Authorization
```
### Test 3: Actual API Request with Origin
Test a real API request with Origin header:
```bash
curl https://backend.example.com/api/snippets \
-H "Origin: https://frontend.example.com" \
-v
```
Should return `Access-Control-Allow-Origin` header and snippet data.
### Test 4: Wrong Origin (Should Fail)
Test that unauthorized origins are blocked:
```bash
curl https://backend.example.com/api/snippets \
-H "Origin: https://malicious-site.com" \
-v
```
Expected: No `Access-Control-Allow-Origin` header (or browser would block).
### Test 5: Browser DevTools Test
1. Open frontend in browser: `https://frontend.example.com`
2. Open DevTools (F12) → Network tab
3. Create a new snippet or load snippets
4. Check the API request:
- Should show status 200 OK
- Response headers should include `Access-Control-Allow-Origin`
- No CORS errors in Console
## Common CORS Errors & Solutions
### Error: "CORS policy: No 'Access-Control-Allow-Origin' header"
**Cause:** Backend is not returning CORS headers
**Solutions:**
1. Verify `CORS_ALLOWED_ORIGINS` is set correctly in backend
2. Check backend logs for errors
3. Ensure Flask-CORS is installed: `pip install flask-cors`
4. Restart backend after environment variable changes
### Error: "CORS policy: Origin 'https://frontend.example.com' not allowed"
**Cause:** Frontend origin not in allowed list
**Solutions:**
1. Check `CORS_ALLOWED_ORIGINS` includes exact frontend URL
2. Ensure no trailing slash: `https://frontend.example.com` not `https://frontend.example.com/`
3. Verify HTTPS vs HTTP matches exactly
4. Check for typos in domain name
### Error: "CORS policy: Request header 'content-type' not allowed"
**Cause:** Backend not allowing required headers
**Solutions:**
1. Verify backend allows `Content-Type` header
2. Check Flask-CORS configuration in `app.py`
3. Ensure `allow_headers` includes `Content-Type`
### Error: Mixed Content (HTTP/HTTPS)
**Cause:** Frontend uses HTTPS but backend uses HTTP (or vice versa)
**Solutions:**
1. Ensure both frontend and backend use HTTPS in production
2. Update `VITE_FLASK_BACKEND_URL` to use `https://`
3. Enable HTTPS in CapRover for both apps
4. Verify Cloudflare SSL/TLS mode is "Full (strict)"
### Error: "CORS policy: Credential is not supported if origin is '*'"
**Cause:** Using `CORS_ALLOWED_ORIGINS=*` with `supports_credentials=True`
**Solutions:**
1. Set specific origins instead of `*`
2. Or disable credentials if not needed
## Deployment Scenarios
### Scenario 1: Separate Domains (Recommended for Production)
```
Frontend: https://frontend.example.com
Backend: https://backend.example.com
```
**Frontend Config:**
```bash
VITE_FLASK_BACKEND_URL=https://backend.example.com
```
**Backend Config:**
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com
```
**Pros:** Clean separation, independent scaling
**Cons:** Requires CORS configuration
### Scenario 2: Single Domain with Proxy
```
Frontend: https://app.example.com
Backend: https://app.example.com/api (proxied)
```
**Frontend Config:**
```bash
VITE_FLASK_BACKEND_URL=/api
```
**Backend Config:**
```bash
CORS_ALLOWED_ORIGINS=* # Not needed if proxied through nginx
```
**Nginx Config:** (already configured in `nginx.conf`)
```nginx
location /api {
proxy_pass http://backend:5000;
}
```
**Pros:** No CORS issues (same-origin), simpler configuration
**Cons:** Tight coupling, single domain
### Scenario 3: Multiple Frontends
```
Frontend 1: https://app.example.com
Frontend 2: https://staging.example.com
Backend: https://api.example.com
```
**Frontend Config (both):**
```bash
VITE_FLASK_BACKEND_URL=https://api.example.com
```
**Backend Config:**
```bash
CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com
```
## Cloudflare-Specific Configuration
### Cloudflare SSL/TLS Mode
Set to **"Full (strict)"** to ensure end-to-end encryption:
1. Cloudflare Dashboard → SSL/TLS → Overview
2. Select "Full (strict)"
3. Ensures both Cloudflare-to-origin and client-to-Cloudflare use SSL
### Cloudflare Always Use HTTPS
1. SSL/TLS → Edge Certificates
2. Enable "Always Use HTTPS"
3. Automatically redirects HTTP to HTTPS
### Cloudflare Transform Rules (Optional)
Add security headers using Transform Rules:
```
Header: Strict-Transport-Security
Value: max-age=31536000; includeSubDomains
Header: X-Content-Type-Options
Value: nosniff
Header: X-Frame-Options
Value: DENY
```
## Automated CORS Testing Script
Save this as `test-cors.sh`:
```bash
#!/bin/bash
FRONTEND_URL="https://frontend.example.com"
BACKEND_URL="https://backend.example.com"
echo "Testing CORS Configuration..."
echo "================================"
echo -e "\n1. Testing Health Endpoint..."
curl -s "$BACKEND_URL/health" | jq .
echo -e "\n2. Testing OPTIONS Preflight..."
curl -X OPTIONS "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Access-Control-Request-Method: GET" \
-i -s | grep -i "access-control"
echo -e "\n3. Testing GET with Origin..."
curl -s "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-i | grep -i "access-control"
echo -e "\n4. Testing POST with Origin..."
curl -X POST "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Content-Type: application/json" \
-d '{"id":"test","title":"Test","code":"test","language":"JavaScript","createdAt":"2024-01-01T00:00:00","updatedAt":"2024-01-01T00:00:00"}' \
-i -s | grep -i "access-control"
echo -e "\nCORS tests complete!"
```
Make it executable and run:
```bash
chmod +x test-cors.sh
./test-cors.sh
```
## Frontend Storage Config Helper
The frontend automatically handles backend configuration through the `getStorageConfig()` function:
### Automatic Configuration
If `VITE_FLASK_BACKEND_URL` is set, the app automatically uses Flask backend:
```typescript
// src/lib/storage.ts
function getDefaultConfig(): StorageConfig {
const flaskUrl = import.meta.env.VITE_FLASK_BACKEND_URL
if (flaskUrl) {
return { backend: 'flask', flaskUrl: flaskUrl }
}
return { backend: 'indexeddb' }
}
```
### Manual Configuration
Users can also manually configure in Settings page (if no env var is set).
## Debugging Tips
### Enable Verbose Logging
Add logging to backend `app.py`:
```python
import logging
logging.basicConfig(level=logging.DEBUG)
@app.after_request
def after_request(response):
app.logger.debug(f"Response headers: {response.headers}")
return response
```
### Browser DevTools
1. Open DevTools (F12)
2. Network tab → Enable "Preserve log"
3. Filter by "Fetch/XHR"
4. Look for OPTIONS and GET/POST requests
5. Check Response Headers for `Access-Control-Allow-Origin`
6. Check Console for CORS error messages
### CapRover Logs
View real-time backend logs:
```bash
# Via CapRover CLI
caprover logs codesnippet-backend --lines 100 --follow
# Via Dashboard
Apps → codesnippet-backend → Logs
```
## Security Best Practices
### Production CORS Checklist
- [ ] Set specific origins (not `*`)
- [ ] Use HTTPS for all URLs
- [ ] Enable Cloudflare proxy (orange cloud)
- [ ] Set Cloudflare SSL mode to "Full (strict)"
- [ ] Remove trailing slashes from origin URLs
- [ ] Test with automated script
- [ ] Monitor for CORS errors in production logs
- [ ] Document allowed origins
### Regular Audits
Periodically review:
1. Which origins are allowed
2. Whether all origins are still needed
3. CORS-related errors in logs
4. Unauthorized access attempts
## Additional Resources
- [MDN CORS Documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
- [Flask-CORS Documentation](https://flask-cors.readthedocs.io/)
- [Cloudflare SSL/TLS Documentation](https://developers.cloudflare.com/ssl/)
- [CapRover Environment Variables](https://caprover.com/docs/app-configuration.html)

190
DEPLOYMENT-CHECKLIST.md Normal file
View File

@@ -0,0 +1,190 @@
# Deployment Checklist
Quick reference checklist for deploying CodeSnippet with separate frontend and backend domains.
## Pre-Deployment
- [ ] CapRover server is running and accessible
- [ ] Domain name configured in DNS provider (Cloudflare)
- [ ] CapRover CLI installed: `npm install -g caprover`
- [ ] Logged into CapRover: `caprover login`
## DNS Configuration (Cloudflare)
- [ ] A record for `frontend.example.com` → CapRover IP (Proxied ☁️)
- [ ] A record for `backend.example.com` → CapRover IP (Proxied ☁️)
- [ ] DNS records propagated (check with `dig` or `nslookup`)
## Backend Deployment
- [ ] Create app in CapRover: `codesnippet-backend`
- [ ] Enable "Has Persistent Data" with path: `/app/data`
- [ ] Set environment variable: `CORS_ALLOWED_ORIGINS=https://frontend.example.com`
- [ ] Set environment variable: `DATABASE_PATH=/app/data/snippets.db`
- [ ] Deploy code: `cd backend && caprover deploy -a codesnippet-backend`
- [ ] Enable HTTPS in CapRover
- [ ] Connect custom domain: `backend.example.com`
- [ ] Force HTTPS redirect enabled
- [ ] SSL certificate issued successfully
- [ ] Test health endpoint: `curl https://backend.example.com/health`
## Frontend Deployment
- [ ] Create app in CapRover: `codesnippet-frontend`
- [ ] Set environment variable: `VITE_FLASK_BACKEND_URL=https://backend.example.com`
- [ ] Deploy code: `caprover deploy -a codesnippet-frontend` (from project root)
- [ ] Enable HTTPS in CapRover
- [ ] Connect custom domain: `frontend.example.com`
- [ ] Force HTTPS redirect enabled
- [ ] SSL certificate issued successfully
- [ ] Test frontend loads: Visit `https://frontend.example.com`
## Cloudflare Configuration
- [ ] SSL/TLS mode set to "Full (strict)"
- [ ] "Always Use HTTPS" enabled
- [ ] "Automatic HTTPS Rewrites" enabled
- [ ] "Auto Minify" enabled (JS, CSS, HTML)
- [ ] "Brotli" compression enabled
## Testing
- [ ] Backend health check responds: `curl https://backend.example.com/health`
- [ ] CORS preflight test passes (see CORS-GUIDE.md)
- [ ] Frontend loads without errors
- [ ] Backend indicator shows "Backend" status (not "Local")
- [ ] Can create new snippet
- [ ] Can view existing snippet
- [ ] Can edit snippet
- [ ] Can delete snippet
- [ ] No CORS errors in browser console (F12)
- [ ] Mobile responsive layout works
## Post-Deployment
- [ ] Database backup strategy configured
- [ ] Monitoring enabled (CapRover metrics)
- [ ] Rate limiting configured (Cloudflare)
- [ ] Error logging reviewed
- [ ] Documentation updated with actual URLs
## Security Verification
- [ ] Both domains use HTTPS only
- [ ] `CORS_ALLOWED_ORIGINS` set to specific domain (not `*`)
- [ ] Flask debug mode disabled (`debug=False`)
- [ ] No sensitive data in environment variables
- [ ] CapRover firewall rules configured
- [ ] Cloudflare security features enabled
## Quick Commands
### Deploy Backend
```bash
cd backend
caprover deploy -a codesnippet-backend
```
### Deploy Frontend
```bash
caprover deploy -a codesnippet-frontend
```
### Check Backend Logs
```bash
caprover logs codesnippet-backend --lines 100 --follow
```
### Check Frontend Logs
```bash
caprover logs codesnippet-frontend --lines 100 --follow
```
### Test CORS
```bash
curl -X OPTIONS https://backend.example.com/api/snippets \
-H "Origin: https://frontend.example.com" \
-H "Access-Control-Request-Method: GET" \
-v
```
### Backup Database
```bash
docker cp captain--codesnippet-backend:/app/data/snippets.db ./backup-$(date +%Y%m%d).db
```
## Troubleshooting Quick Fixes
### Frontend can't connect to backend
1. Check `VITE_FLASK_BACKEND_URL` in frontend environment variables
2. Verify backend is running: `curl https://backend.example.com/health`
3. Check CORS configuration in backend
### CORS errors in browser
1. Verify `CORS_ALLOWED_ORIGINS` includes frontend URL exactly
2. Ensure both use HTTPS (not mixed HTTP/HTTPS)
3. Restart backend app after environment changes
### SSL certificate issues
1. Wait 5-10 minutes for Let's Encrypt
2. Verify DNS records point to CapRover
3. Disable and re-enable HTTPS in CapRover
### Data lost after restart
1. Verify "Has Persistent Data" enabled in backend app
2. Check persistent directory path: `/app/data`
3. Verify volume is mounted correctly
## Environment Variables Quick Reference
### Backend (codesnippet-backend)
```
CORS_ALLOWED_ORIGINS=https://frontend.example.com
DATABASE_PATH=/app/data/snippets.db
```
### Frontend (codesnippet-frontend)
```
VITE_FLASK_BACKEND_URL=https://backend.example.com
```
## Alternative: Single Domain Deployment
For single domain setup (`https://app.example.com`):
### Backend
- [ ] Deploy backend (internal only, no custom domain)
- [ ] Set `CORS_ALLOWED_ORIGINS=*` (not needed if proxied)
### Frontend
- [ ] Set `VITE_FLASK_BACKEND_URL=/api`
- [ ] nginx proxies `/api` to backend (already configured)
- [ ] Deploy with custom domain: `app.example.com`
Benefits: No CORS issues, simpler DNS
Drawbacks: Tightly coupled services
## Rollback Plan
If deployment fails:
1. **Frontend issues:** Redeploy previous version
2. **Backend issues:** Check logs, fix errors, redeploy
3. **Database corruption:** Restore from backup
4. **DNS issues:** Verify Cloudflare settings
5. **SSL issues:** Disable HTTPS temporarily, debug, re-enable
## Maintenance Schedule
- **Daily:** Check error logs
- **Weekly:** Review metrics and performance
- **Monthly:** Update dependencies, security patches
- **Quarterly:** Review and rotate secrets/keys
## Support Resources
- [Full Deployment Guide](./DEPLOYMENT.md)
- [CORS Configuration Guide](./CORS-GUIDE.md)
- [Backend Configuration](./BACKEND-CONFIG.md)
- [CapRover Documentation](https://caprover.com/docs/)
- [Cloudflare Documentation](https://developers.cloudflare.com/)

380
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,380 @@
# CapRover / Cloudflare Deployment Guide
This guide explains how to deploy CodeSnippet with separate frontend and backend domains using CapRover and Cloudflare.
## Architecture
```
┌─────────────────────────────────────────────┐
│ Cloudflare DNS & Proxy │
│ https://frontend.example.com │
│ https://backend.example.com │
└─────────────┬───────────────────────────────┘
│ HTTPS (Cloudflare SSL)
┌─────────────▼───────────────────────────────┐
│ CapRover Server │
│ │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ Frontend │ │ Backend │ │
│ │ (Nginx) │ │ (Flask) │ │
│ │ Port 3000 │ │ Port 5000 │ │
│ └──────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────┘
```
## Prerequisites
1. CapRover server installed and configured
2. Domain name configured in Cloudflare
3. DNS records pointing to your CapRover server
4. CapRover CLI installed: `npm install -g caprover`
## Step 1: Configure DNS in Cloudflare
Add these DNS records in Cloudflare:
| Type | Name | Content | Proxy Status |
|------|----------|----------------------|--------------|
| A | frontend | YOUR_CAPROVER_IP | Proxied |
| A | backend | YOUR_CAPROVER_IP | Proxied |
**Important:** Enable "Proxied" (orange cloud) to use Cloudflare's CDN and SSL.
## Step 2: Deploy Backend to CapRover
### Create Backend App
1. Login to CapRover dashboard
2. Go to "Apps" → "One-Click Apps/Databases"
3. Create a new app named `codesnippet-backend`
4. Enable "Has Persistent Data" and set persistent directory to `/app/data`
### Configure Backend Environment Variables
In the backend app settings, add these environment variables:
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com
DATABASE_PATH=/app/data/snippets.db
```
### Deploy Backend Code
From the `backend` directory:
```bash
cd backend
caprover deploy -a codesnippet-backend
```
### Enable HTTPS for Backend
1. In CapRover dashboard → Apps → codesnippet-backend
2. Go to "HTTP Settings"
3. Enable "HTTPS"
4. Connect domain: `backend.example.com`
5. Enable "Force HTTPS by redirecting all HTTP traffic to HTTPS"
6. Wait for SSL certificate to be issued
## Step 3: Deploy Frontend to CapRover
### Create Frontend App
1. In CapRover dashboard, create new app: `codesnippet-frontend`
2. No persistent data needed for frontend
### Configure Frontend Environment Variables
In the frontend app settings, add:
```bash
VITE_FLASK_BACKEND_URL=https://backend.example.com
```
### Deploy Frontend Code
From the project root:
```bash
caprover deploy -a codesnippet-frontend
```
### Enable HTTPS for Frontend
1. In CapRover dashboard → Apps → codesnippet-frontend
2. Go to "HTTP Settings"
3. Enable "HTTPS"
4. Connect domain: `frontend.example.com`
5. Enable "Force HTTPS by redirecting all HTTP traffic to HTTPS"
6. Wait for SSL certificate to be issued
## Step 4: Configure Cloudflare Settings
### SSL/TLS Settings
1. Go to Cloudflare dashboard → SSL/TLS
2. Set encryption mode to "Full (strict)"
3. Enable "Always Use HTTPS"
4. Enable "Automatic HTTPS Rewrites"
### Security Settings
1. Go to Security → WAF
2. Consider enabling Bot Fight Mode for backend
3. Set up rate limiting rules if needed
### Speed Settings
1. Enable "Auto Minify" for JavaScript, CSS, HTML
2. Enable "Brotli" compression
3. Set Browser Cache TTL appropriately
## Step 5: Verify Deployment
### Test Backend
```bash
curl https://backend.example.com/health
```
Expected response:
```json
{
"status": "healthy",
"timestamp": "2024-01-01T12:00:00.000000"
}
```
### Test CORS
```bash
curl -H "Origin: https://frontend.example.com" \
-H "Access-Control-Request-Method: GET" \
-X OPTIONS \
https://backend.example.com/api/snippets
```
Should return CORS headers:
```
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```
### Test Frontend
Visit `https://frontend.example.com` and verify:
- Frontend loads correctly
- Backend indicator shows "Backend" status
- Can create, edit, and delete snippets
- No CORS errors in browser console
## Environment Variables Reference
### Backend Environment Variables
| Variable | Description | Example |
|-----------------------|------------------------------------------------|----------------------------------|
| `CORS_ALLOWED_ORIGINS`| Comma-separated list of allowed frontend URLs | `https://frontend.example.com` |
| `DATABASE_PATH` | Path to SQLite database file | `/app/data/snippets.db` |
**Note:** Use `*` for `CORS_ALLOWED_ORIGINS` only in development. In production, always specify exact origins.
### Frontend Environment Variables
| Variable | Description | Example |
|---------------------------|------------------------------|--------------------------------|
| `VITE_FLASK_BACKEND_URL` | Backend API URL | `https://backend.example.com` |
## Alternative: Single Domain Setup
If you prefer a single domain (e.g., `https://app.example.com`), you can deploy frontend with nginx proxying to backend:
### Deploy Both Services
1. Deploy backend as before (internal only, no custom domain)
2. Frontend proxies `/api` requests to backend via nginx
### Frontend Configuration
```bash
# Frontend environment variables
VITE_FLASK_BACKEND_URL=/api
```
### Nginx Configuration
The included `nginx.conf` already handles this:
```nginx
location /api {
proxy_pass http://backend:5000;
# ... proxy headers
}
```
### Benefits
- Simplified DNS (single domain)
- No CORS issues (same-origin requests)
- Easier SSL management
### Drawbacks
- Frontend and backend tightly coupled
- Can't independently scale services
- Single point of failure
## Troubleshooting
### CORS Errors
**Problem:** Browser console shows CORS errors
**Solutions:**
1. Verify `CORS_ALLOWED_ORIGINS` in backend matches frontend URL exactly
2. Ensure both domains use HTTPS (not mixed HTTP/HTTPS)
3. Check Cloudflare proxy status is enabled for both domains
4. Clear browser cache and hard refresh
### Backend Connection Failed
**Problem:** Frontend shows "Connection failed" error
**Solutions:**
1. Verify backend is running: `curl https://backend.example.com/health`
2. Check CapRover logs for backend app
3. Verify `VITE_FLASK_BACKEND_URL` in frontend matches backend URL
4. Test from command line: `curl -v https://backend.example.com/api/snippets`
### SSL Certificate Issues
**Problem:** SSL certificate not issued or invalid
**Solutions:**
1. Wait 5-10 minutes for Let's Encrypt to issue certificate
2. Verify DNS records are correct and propagated
3. Check CapRover can reach Let's Encrypt (port 80 open)
4. Try disabling and re-enabling HTTPS in CapRover
### Data Persistence Issues
**Problem:** Backend loses data after restart
**Solutions:**
1. Verify "Has Persistent Data" is enabled in CapRover
2. Check persistent directory path is `/app/data`
3. Verify `DATABASE_PATH` environment variable is correct
4. Check CapRover volume is properly mounted
### Multiple Origins
**Problem:** Need to allow multiple frontend domains
**Solution:** Set comma-separated origins:
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com,https://staging.example.com
```
## Security Best Practices
### Production Checklist
- [ ] HTTPS enabled for both frontend and backend
- [ ] `CORS_ALLOWED_ORIGINS` set to specific domains (not `*`)
- [ ] Cloudflare proxy enabled (orange cloud)
- [ ] Rate limiting configured in Cloudflare
- [ ] Backend database backed up regularly
- [ ] Environment variables stored securely (not in code)
- [ ] Debug mode disabled in Flask (`debug=False`)
- [ ] Cloudflare WAF rules configured
- [ ] HTTPS-only cookies enabled
- [ ] Security headers configured in nginx
### Recommended Cloudflare Rules
1. **Rate Limiting:** Limit API requests to 100 per minute per IP
2. **Bot Protection:** Challenge or block known bad bots
3. **Geographic Restrictions:** Block countries you don't serve (optional)
4. **DDoS Protection:** Enable automatic DDoS mitigation
## Monitoring
### CapRover Monitoring
1. Enable app metrics in CapRover dashboard
2. Monitor CPU and memory usage
3. Set up alerts for app crashes
4. Review logs regularly for errors
### Cloudflare Analytics
1. Monitor traffic patterns
2. Check for unusual spikes or attacks
3. Review security events
4. Analyze performance metrics
## Backup Strategy
### Automated Backups
Set up a cron job in CapRover to backup database:
```bash
# In backend app settings, add a schedule:
0 2 * * * tar -czf /app/data/backup-$(date +%Y%m%d).tar.gz /app/data/snippets.db
```
### Manual Backup
1. SSH into CapRover server
2. Copy database file:
```bash
docker cp captain--codesnippet-backend:/app/data/snippets.db ./backup.db
```
### Restore from Backup
1. Copy backup to container:
```bash
docker cp ./backup.db captain--codesnippet-backend:/app/data/snippets.db
```
2. Restart backend app in CapRover
## Scaling Considerations
### Vertical Scaling
Increase resources in CapRover:
1. Go to app settings → "Resources"
2. Increase CPU and memory limits
3. Restart app
### Horizontal Scaling
For high traffic:
1. Deploy multiple backend instances in CapRover
2. Use CapRover's load balancing
3. Consider shared database (PostgreSQL instead of SQLite)
4. Use Redis for session management
## Cost Optimization
### Cloudflare
- Free plan includes SSL, CDN, and basic DDoS protection
- Pro plan ($20/mo) adds WAF and additional performance features
### CapRover
- Single VPS can run both frontend and backend
- Recommended: 2 CPU / 4GB RAM minimum
- Estimated cost: $10-20/month (DigitalOcean, Linode, Vultr)
## Support
For issues specific to:
- **CapRover:** https://caprover.com/docs/
- **Cloudflare:** https://support.cloudflare.com/
- **CodeSnippet:** Check the main README.md and BACKEND-CONFIG.md

329
ENV-CONFIG.md Normal file
View File

@@ -0,0 +1,329 @@
# Environment Configuration Examples
This directory contains example environment configurations for different deployment scenarios.
## Quick Reference
Copy the appropriate example to `.env` in the project root:
```bash
cp .env.example .env
# Edit .env with your values
```
## Scenarios
### 1. Local Development (Default)
**Use case:** Developing frontend only with local browser storage
```bash
# No backend URL - uses IndexedDB
VITE_FLASK_BACKEND_URL=
```
### 2. Local Development with Backend
**Use case:** Developing with local Flask backend
```bash
# Frontend (.env)
VITE_FLASK_BACKEND_URL=http://localhost:5000
# Backend (environment or .env)
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
DATABASE_PATH=/app/data/snippets.db
```
### 3. Docker Compose (Full Stack)
**Use case:** Running both frontend and backend with Docker
```bash
# Frontend automatically connects to backend via nginx proxy
# No .env needed - configured in docker-compose.yml
```
**docker-compose.yml already includes:**
```yaml
environment:
- VITE_FLASK_BACKEND_URL=http://localhost:5000
- CORS_ALLOWED_ORIGINS=http://localhost:3000
- DATABASE_PATH=/app/data/snippets.db
```
### 4. Production - Separate Domains (CapRover)
**Use case:** Frontend and backend on different domains
```bash
# Frontend environment variables (in CapRover)
VITE_FLASK_BACKEND_URL=https://backend.example.com
# Backend environment variables (in CapRover)
CORS_ALLOWED_ORIGINS=https://frontend.example.com
DATABASE_PATH=/app/data/snippets.db
```
### 5. Production - Multiple Frontend Domains
**Use case:** Multiple frontend deployments (prod, staging, dev)
```bash
# Backend environment variables
CORS_ALLOWED_ORIGINS=https://app.example.com,https://staging.example.com,https://dev.example.com
DATABASE_PATH=/app/data/snippets.db
# Frontend 1 (Production)
VITE_FLASK_BACKEND_URL=https://api.example.com
# Frontend 2 (Staging)
VITE_FLASK_BACKEND_URL=https://api.example.com
# Frontend 3 (Development)
VITE_FLASK_BACKEND_URL=https://api.example.com
```
### 6. Production - Single Domain (Proxied)
**Use case:** Frontend and backend on same domain, nginx proxy
```bash
# Frontend environment variables
VITE_FLASK_BACKEND_URL=/api
# Backend environment variables
CORS_ALLOWED_ORIGINS=*
# Note: CORS not needed since nginx proxies requests (same-origin)
DATABASE_PATH=/app/data/snippets.db
```
**nginx.conf includes:**
```nginx
location /api {
proxy_pass http://backend:5000;
}
```
## Environment Variables Reference
### Frontend Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `VITE_FLASK_BACKEND_URL` | Backend API URL. When set, forces Flask backend usage. | `https://backend.example.com` |
**Notes:**
- Must start with `VITE_` to be exposed to frontend
- If not set, app uses IndexedDB (local storage)
- Can be relative (`/api`) if using nginx proxy
- Requires rebuild for production: `npm run build`
### Backend Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed origins | `https://frontend.example.com` |
| `DATABASE_PATH` | Path to SQLite database file | `/app/data/snippets.db` |
**Notes:**
- Use `*` only in development
- In production, always specify exact origins
- No trailing slashes on URLs
- Must match frontend URL exactly (including https://)
## Setting Environment Variables
### Local Development (.env file)
```bash
# Create .env file
cat > .env << EOF
VITE_FLASK_BACKEND_URL=http://localhost:5000
EOF
# Start dev server
npm run dev
```
### Docker Build
```bash
# Build with environment variable
docker build --build-arg VITE_FLASK_BACKEND_URL=https://backend.example.com -t frontend .
```
### Docker Compose
```yaml
# docker-compose.yml
services:
frontend:
environment:
- VITE_FLASK_BACKEND_URL=https://backend.example.com
```
### CapRover
1. Go to CapRover dashboard
2. Select your app
3. Click "App Configs" tab
4. Add environment variables in "Environment Variables" section
5. Redeploy app
### Kubernetes
```yaml
# deployment.yaml
env:
- name: VITE_FLASK_BACKEND_URL
value: "https://backend.example.com"
- name: CORS_ALLOWED_ORIGINS
value: "https://frontend.example.com"
```
## Testing Configuration
### Test Frontend Backend Connection
```bash
# Check if environment variable is set
echo $VITE_FLASK_BACKEND_URL
# Check in browser console
console.log(import.meta.env.VITE_FLASK_BACKEND_URL)
```
### Test Backend CORS
```bash
# Quick test
curl -H "Origin: https://frontend.example.com" \
-H "Access-Control-Request-Method: GET" \
-X OPTIONS \
https://backend.example.com/api/snippets
# Full test suite
./test-cors.sh https://backend.example.com https://frontend.example.com
```
## Common Mistakes
### ❌ Wrong: Missing VITE_ Prefix
```bash
FLASK_BACKEND_URL=http://localhost:5000 # Won't work!
```
### ✅ Correct: VITE_ Prefix Required
```bash
VITE_FLASK_BACKEND_URL=http://localhost:5000
```
---
### ❌ Wrong: Trailing Slash in CORS
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com/ # Won't work!
```
### ✅ Correct: No Trailing Slash
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com
```
---
### ❌ Wrong: HTTP/HTTPS Mismatch
```bash
# Frontend
VITE_FLASK_BACKEND_URL=https://backend.example.com
# Backend
CORS_ALLOWED_ORIGINS=http://frontend.example.com # Wrong protocol!
```
### ✅ Correct: Matching Protocols
```bash
# Frontend
VITE_FLASK_BACKEND_URL=https://backend.example.com
# Backend
CORS_ALLOWED_ORIGINS=https://frontend.example.com
```
---
### ❌ Wrong: Using * in Production
```bash
CORS_ALLOWED_ORIGINS=* # Security risk in production!
```
### ✅ Correct: Specific Origins
```bash
CORS_ALLOWED_ORIGINS=https://frontend.example.com
```
## Troubleshooting
### Environment variable not working
1. **Frontend not rebuilding:**
- Vite requires rebuild for env vars: `npm run build`
- Dev server: Restart `npm run dev`
2. **Variable not prefixed correctly:**
- Must start with `VITE_` for frontend
- Backend vars don't need prefix
3. **Docker not picking up changes:**
- Rebuild: `docker-compose up -d --build`
- Check: `docker-compose config`
### CORS errors persist
1. **Backend not restarted:**
- Restart after env changes
- Check logs: `docker-compose logs backend`
2. **URL mismatch:**
- Frontend URL must match CORS_ALLOWED_ORIGINS exactly
- Check browser console for actual origin
3. **Cloudflare issues:**
- Verify proxy status (orange cloud)
- Check SSL/TLS mode: "Full (strict)"
## Security Best Practices
### Development
- ✅ Use `*` for CORS_ALLOWED_ORIGINS
- ✅ Use http:// for local URLs
- ✅ Keep .env out of version control
### Staging
- ✅ Use specific origins for CORS
- ✅ Use https:// for all URLs
- ✅ Test with production-like configuration
### Production
- ✅ Always use specific origins
- ✅ Always use https://
- ✅ Store secrets in secure environment
- ✅ Never commit .env files
- ✅ Rotate credentials regularly
- ✅ Monitor access logs
## Additional Resources
- [Backend Configuration Guide](./BACKEND-CONFIG.md)
- [CORS Configuration & Testing](./CORS-GUIDE.md)
- [Deployment Guide](./DEPLOYMENT.md)
- [Deployment Checklist](./DEPLOYMENT-CHECKLIST.md)

View File

@@ -74,10 +74,18 @@ When set, the app automatically connects to Flask backend and disables manual co
## 📚 Documentation ## 📚 Documentation
### Getting Started
- **[Quick Start Guide](./QUICKSTART.md)** - Get up and running quickly - **[Quick Start Guide](./QUICKSTART.md)** - Get up and running quickly
- **[Application Guide](./README-APP.md)** - Features and usage - **[Application Guide](./README-APP.md)** - Features and usage
- **[Backend Configuration](./BACKEND-CONFIG.md)** - Detailed backend setup
### Backend & Storage
- **[Backend Configuration](./BACKEND-CONFIG.md)** - Detailed backend setup and environment variables
- **[Backend API](./backend/README.md)** - Flask API documentation - **[Backend API](./backend/README.md)** - Flask API documentation
### Production Deployment
- **[Deployment Guide](./DEPLOYMENT.md)** - Complete CapRover/Cloudflare deployment walkthrough
- **[CORS Configuration](./CORS-GUIDE.md)** - CORS setup and troubleshooting
- **[Deployment Checklist](./DEPLOYMENT-CHECKLIST.md)** - Quick deployment reference
- **[Docker Examples](./docker-compose.README.md)** - Docker deployment options - **[Docker Examples](./docker-compose.README.md)** - Docker deployment options
## 🛠️ Technology Stack ## 🛠️ Technology Stack

View File

@@ -7,10 +7,12 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY app.py . COPY app.py .
RUN mkdir -p /data RUN mkdir -p /app/data
ENV FLASK_APP=app.py
ENV DATABASE_PATH=/app/data/snippets.db
ENV CORS_ALLOWED_ORIGINS=*
EXPOSE 5000 EXPOSE 5000
ENV DB_PATH=/data/snippets.db
CMD ["python", "app.py"] CMD ["python", "app.py"]

View File

@@ -113,10 +113,14 @@ docker build -t codesnippet-backend ./backend
```bash ```bash
# With volume for persistent data # With volume for persistent data
docker run -p 5000:5000 -v $(pwd)/data:/data codesnippet-backend docker run -p 5000:5000 -v $(pwd)/data:/app/data codesnippet-backend
# With custom database path # With custom database path and CORS
docker run -p 5000:5000 -e DB_PATH=/data/custom.db -v $(pwd)/data:/data codesnippet-backend docker run -p 5000:5000 \
-e DATABASE_PATH=/app/data/custom.db \
-e CORS_ALLOWED_ORIGINS=https://frontend.example.com \
-v $(pwd)/data:/app/data \
codesnippet-backend
``` ```
### Using Docker Compose ### Using Docker Compose
@@ -162,7 +166,24 @@ CREATE TABLE snippets (
## Environment Variables ## Environment Variables
- `DB_PATH` - Path to SQLite database file (default: `/data/snippets.db`) | Variable | Description | Default | Example |
|----------|-------------|---------|---------|
| `DATABASE_PATH` | Path to SQLite database file | `/app/data/snippets.db` | `/app/data/snippets.db` |
| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed frontend origins | `*` (all origins) | `https://frontend.example.com` |
### Production Configuration
For production deployments, always set specific CORS origins:
```bash
# Single origin
export CORS_ALLOWED_ORIGINS=https://frontend.example.com
# Multiple origins
export CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com
```
**Important:** Using `*` for CORS in production is a security risk. Only use in development.
## Troubleshooting ## Troubleshooting
@@ -172,9 +193,34 @@ CREATE TABLE snippets (
- Verify the port (5000) is not in use - Verify the port (5000) is not in use
### CORS Errors ### CORS Errors
- The backend allows all origins by default - The backend allows all origins by default in development (`CORS_ALLOWED_ORIGINS=*`)
- Modify `CORS(app)` in `app.py` if you need to restrict origins - For production, set specific origins: `CORS_ALLOWED_ORIGINS=https://frontend.example.com`
- See [CORS-GUIDE.md](../CORS-GUIDE.md) for detailed CORS configuration and testing
- Verify frontend URL matches exactly (including https:// and no trailing slash)
### Database Locked ### Database Locked
- Ensure only one instance of the backend is running - Ensure only one instance of the backend is running
- Check file permissions on the database file - Check file permissions on the database file
## Production Deployment
For deploying to production with separate frontend and backend domains:
1. **See [DEPLOYMENT.md](../DEPLOYMENT.md)** - Complete CapRover/Cloudflare deployment guide
2. **See [CORS-GUIDE.md](../CORS-GUIDE.md)** - CORS configuration and testing
3. **See [DEPLOYMENT-CHECKLIST.md](../DEPLOYMENT-CHECKLIST.md)** - Quick deployment checklist
### Quick Production Setup
```bash
# Build and deploy backend to CapRover
cd backend
caprover deploy -a codesnippet-backend
# Set environment variables in CapRover dashboard:
# - CORS_ALLOWED_ORIGINS=https://frontend.example.com
# - DATABASE_PATH=/app/data/snippets.db
# Enable persistent storage at /app/data
# Enable HTTPS and connect custom domain
```

View File

@@ -1,181 +1,191 @@
from flask import Flask, request, jsonify from flask import Flask, request, jsonify
from flask_cors import CORS from flask_cors import CORS
from datetime from datetime import datetime
import sqlite3
from datetime import datetime import json
import os import os
app = Flask(__name__) app = Flask(__name__)
ALLOWED_ORIGINS = os.environ.get('CORS_ALLOWED_ORIGINS', '*')
cursor = conn.cursor() if ALLOWED_ORIGINS == '*':
CORS(app,
i origins='*',
code TEXT NOT NULL, methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
description TEXT, allow_headers=['Content-Type', 'Authorization'],
cat supports_credentials=False)
else:
up origins_list = [origin.strip() for origin in ALLOWED_ORIGINS.split(',')]
''') CORS(app,
conn.commit() origins=origins_list,
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
def allow_headers=['Content-Type', 'Authorization'],
supports_credentials=True)
def get_snippets():
conn = get_db() DATABASE_PATH = os.environ.get('DATABASE_PATH', '/app/data/snippets.db')
cursor.execute('SELECT * os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
conn.close()
snippets = [] def get_db():
snippet = dict(ro conn = sqlite3.connect(DATABASE_PATH)
snippe conn.row_factory = sqlite3.Row
snippet['p return conn
except Exception as e: def init_db():
conn = get_db()
updatedAt TEXT NOT NULL cursor = conn.cursor()
) cursor.execute('''
''') CREATE TABLE IF NOT EXISTS snippets (
id TEXT PRIMARY KEY,
conn.commit() title TEXT NOT NULL,
conn.close() code TEXT NOT NULL,
language TEXT NOT NULL,
@app.route('/health', methods=['GET']) description TEXT,
def health(): tags TEXT,
return jsonify({'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()}) category TEXT DEFAULT 'general',
componentName TEXT,
@app.route('/api/snippets', methods=['GET']) previewParams TEXT,
def get_snippets(): createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL
conn = get_db() )
preview_params_json = ''')
cursor.execute('SELECT * FROM snippets ORDER BY updatedAt DESC') conn.commit()
rows = cursor.fetchall() conn.close()
conn.close()
@app.route('/health', methods=['GET'])
snippets = [] def health():
for row in rows: return jsonify({'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()})
snippet = dict(row)
if snippet.get('tags'): @app.route('/api/snippets', methods=['GET'])
snippet['tags'] = json.loads(snippet['tags']) def get_snippets():
if snippet.get('previewParams'): try:
snippet['previewParams'] = json.loads(snippet['previewParams']) conn = get_db()
snippets.append(snippet) cursor = conn.cursor()
cursor.execute('SELECT * FROM snippets ORDER BY updatedAt DESC')
return jsonify(snippets) rows = cursor.fetchall()
except Exception as e: conn.close()
return jsonify({'error': str(e)}), 500
snippets = []
@app.route('/api/snippets/<snippet_id>', methods=['GET']) for row in rows:
UPDATE snippets snippet = dict(row)
if snippet.get('tags'):
data['title snippet['tags'] = json.loads(snippet['tags'])
data['language'], if snippet.get('previewParams'):
tags_json, snippet['previewParams'] = json.loads(snippet['previewParams'])
data.get('component snippets.append(snippet)
data['up
return jsonify(snippets)
conn.commit except Exception as e:
return jsonify({'error': str(e)}), 500
return jsonify(data @app.route('/api/snippets/<snippet_id>', methods=['GET'])
return jsonify({'error' def get_snippet(snippet_id):
@app.route('/api/snippets/<snippet_id>', methods=['DELETE try:
try: conn = get_db()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('SELECT * FROM snippets WHERE id = ?', (snippet_id,))
row = cursor.fetchone()
return jsonify conn.close()
return jsonify({'success': True})
if not row:
if __name__ == '__main__': return jsonify({'error': 'Snippet not found'}), 404
app.run(host='0.0
snippet = dict(row)
if snippet.get('tags'):
snippet['tags'] = json.loads(snippet['tags'])
if snippet.get('previewParams'):
snippet['previewParams'] = json.loads(snippet['previewParams'])
return jsonify(snippet)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/snippets', methods=['POST'])
def create_snippet():
try:
data = request.json
conn = get_db()
cursor = conn.cursor()
tags_json = json.dumps(data.get('tags', []))
preview_params_json = json.dumps(data.get('previewParams', {}))
cursor.execute('''
INSERT INTO snippets (id, title, code, language, description, tags, category, componentName, previewParams, createdAt, updatedAt)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
data['id'],
data['title'],
data['code'],
data['language'],
data.get('description', ''),
tags_json,
data.get('category', 'general'),
data.get('componentName', ''),
preview_params_json,
data['createdAt'],
data['updatedAt']
))
conn.commit()
conn.close()
return jsonify(data), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/snippets/<snippet_id>', methods=['PUT'])
UPDATE snippets def update_snippet(snippet_id):
SET title = ?, code = ?, language = ?, description = ?, tags = ?, category = ?, componentName = ?, previewParams = ?, updatedAt = ? try:
WHERE id = ? data = request.json
''', ( conn = get_db()
data['title'], cursor = conn.cursor()
data['code'],
data['language'], tags_json = json.dumps(data.get('tags', []))
data.get('description', ''), preview_params_json = json.dumps(data.get('previewParams', {}))
tags_json,
data.get('category', 'general'), cursor.execute('''
data.get('componentName', ''), UPDATE snippets
preview_params_json, SET title = ?, code = ?, language = ?, description = ?, tags = ?, category = ?, componentName = ?, previewParams = ?, updatedAt = ?
data['updatedAt'], WHERE id = ?
snippet_id ''', (
)) data['title'],
data['code'],
conn.commit() data['language'],
conn.close() data.get('description', ''),
tags_json,
if cursor.rowcount == 0: data.get('category', 'general'),
return jsonify({'error': 'Snippet not found'}), 404 data.get('componentName', ''),
preview_params_json,
return jsonify(data) data['updatedAt'],
except Exception as e: snippet_id
return jsonify({'error': str(e)}), 500 ))
@app.route('/api/snippets/<snippet_id>', methods=['DELETE']) conn.commit()
def delete_snippet(snippet_id): conn.close()
try:
conn = get_db() if cursor.rowcount == 0:
cursor = conn.cursor() return jsonify({'error': 'Snippet not found'}), 404
cursor.execute('DELETE FROM snippets WHERE id = ?', (snippet_id,))
conn.commit() return jsonify(data)
conn.close() except Exception as e:
return jsonify({'error': str(e)}), 500
if cursor.rowcount == 0:
return jsonify({'error': 'Snippet not found'}), 404 @app.route('/api/snippets/<snippet_id>', methods=['DELETE'])
def delete_snippet(snippet_id):
return jsonify({'success': True}) try:
except Exception as e: conn = get_db()
return jsonify({'error': str(e)}), 500 cursor = conn.cursor()
cursor.execute('DELETE FROM snippets WHERE id = ?', (snippet_id,))
if __name__ == '__main__': conn.commit()
init_db() conn.close()
app.run(host='0.0.0.0', port=5000, debug=True)
if cursor.rowcount == 0:
return jsonify({'error': 'Snippet not found'}), 404
return jsonify({'success': True})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
init_db()
app.run(host='0.0.0.0', port=5000, debug=False)

View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}

4
captain-definition Normal file
View File

@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}

View File

@@ -0,0 +1,35 @@
version: '3.8'
services:
backend:
build: ./backend
ports:
- "5000:5000"
volumes:
- snippet-data:/app/data
environment:
- DATABASE_PATH=/app/data/snippets.db
- CORS_ALLOWED_ORIGINS=https://frontend.example.com,https://app.example.com
restart: unless-stopped
networks:
- app-network
frontend:
build:
context: .
args:
- VITE_FLASK_BACKEND_URL=https://backend.example.com
ports:
- "3000:3000"
depends_on:
- backend
restart: unless-stopped
networks:
- app-network
volumes:
snippet-data:
networks:
app-network:
driver: bridge

View File

@@ -6,9 +6,10 @@ services:
ports: ports:
- "5000:5000" - "5000:5000"
volumes: volumes:
- snippet-data:/data - snippet-data:/app/data
environment: environment:
- DB_PATH=/data/snippets.db - DATABASE_PATH=/app/data/snippets.db
- CORS_ALLOWED_ORIGINS=http://localhost:3000
restart: unless-stopped restart: unless-stopped
frontend: frontend:
@@ -18,8 +19,6 @@ services:
- VITE_FLASK_BACKEND_URL=http://localhost:5000 - VITE_FLASK_BACKEND_URL=http://localhost:5000
ports: ports:
- "3000:3000" - "3000:3000"
environment:
- VITE_FLASK_BACKEND_URL=http://backend:5000
depends_on: depends_on:
- backend - backend
restart: unless-stopped restart: unless-stopped

View File

@@ -6,6 +6,10 @@ server {
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
} }
location /api { location /api {
@@ -14,6 +18,12 @@ server {
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade'; proxy_set_header Connection 'upgrade';
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade; proxy_cache_bypass $http_upgrade;
proxy_buffering off;
proxy_request_buffering off;
} }
} }

138
test-cors.sh Normal file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
# CORS Testing Script for CodeSnippet
# Usage: ./test-cors.sh [BACKEND_URL] [FRONTEND_URL]
# Example: ./test-cors.sh https://backend.example.com https://frontend.example.com
BACKEND_URL="${1:-http://localhost:5000}"
FRONTEND_URL="${2:-http://localhost:3000}"
echo "======================================"
echo "CodeSnippet CORS Testing Script"
echo "======================================"
echo "Backend URL: $BACKEND_URL"
echo "Frontend URL: $FRONTEND_URL"
echo "======================================"
echo ""
# Test 1: Health Check
echo "🔍 Test 1: Health Check (No CORS required)"
echo "--------------------------------------"
HEALTH_RESPONSE=$(curl -s "$BACKEND_URL/health")
if [ $? -eq 0 ]; then
echo "✅ Health check successful"
echo "Response: $HEALTH_RESPONSE"
else
echo "❌ Health check failed - backend may not be running"
exit 1
fi
echo ""
# Test 2: OPTIONS Preflight
echo "🔍 Test 2: OPTIONS Preflight Request"
echo "--------------------------------------"
PREFLIGHT_HEADERS=$(curl -s -X OPTIONS "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Content-Type" \
-i | grep -i "access-control")
if echo "$PREFLIGHT_HEADERS" | grep -q "access-control-allow-origin"; then
echo "✅ CORS preflight successful"
echo "$PREFLIGHT_HEADERS"
else
echo "❌ CORS preflight failed - missing CORS headers"
echo "Response headers:"
curl -s -X OPTIONS "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Access-Control-Request-Method: GET" \
-i | head -n 20
fi
echo ""
# Test 3: GET with Origin
echo "🔍 Test 3: GET Request with Origin"
echo "--------------------------------------"
GET_HEADERS=$(curl -s "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-i | grep -i "access-control")
if echo "$GET_HEADERS" | grep -q "access-control-allow-origin"; then
echo "✅ GET request CORS successful"
echo "$GET_HEADERS"
else
echo "❌ GET request CORS failed - missing CORS headers"
fi
echo ""
# Test 4: POST with Origin
echo "🔍 Test 4: POST Request with Origin"
echo "--------------------------------------"
TEST_SNIPPET='{
"id": "test-cors-'$(date +%s)'",
"title": "CORS Test Snippet",
"code": "console.log(\"CORS test\");",
"language": "JavaScript",
"description": "Test snippet for CORS validation",
"tags": ["test"],
"category": "general",
"createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'",
"updatedAt": "'$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")'"
}'
POST_RESPONSE=$(curl -s -X POST "$BACKEND_URL/api/snippets" \
-H "Origin: $FRONTEND_URL" \
-H "Content-Type: application/json" \
-d "$TEST_SNIPPET" \
-i)
POST_HEADERS=$(echo "$POST_RESPONSE" | grep -i "access-control")
POST_STATUS=$(echo "$POST_RESPONSE" | head -n 1)
if echo "$POST_HEADERS" | grep -q "access-control-allow-origin"; then
echo "✅ POST request CORS successful"
echo "Status: $POST_STATUS"
echo "$POST_HEADERS"
else
echo "❌ POST request CORS failed - missing CORS headers"
echo "Status: $POST_STATUS"
fi
echo ""
# Test 5: Wrong Origin (Should fail or return no CORS headers)
echo "🔍 Test 5: Request from Unauthorized Origin"
echo "--------------------------------------"
WRONG_ORIGIN="https://malicious-site.com"
WRONG_HEADERS=$(curl -s "$BACKEND_URL/api/snippets" \
-H "Origin: $WRONG_ORIGIN" \
-i | grep -i "access-control")
if [ -z "$WRONG_HEADERS" ]; then
echo "✅ Correctly blocking unauthorized origin"
echo " (No CORS headers returned for $WRONG_ORIGIN)"
elif echo "$WRONG_HEADERS" | grep -q "access-control-allow-origin.*\*"; then
echo "⚠️ Warning: Backend allows all origins (*)"
echo " This is fine for development but should be restricted in production"
else
echo "⚠️ Unexpected CORS response for unauthorized origin"
echo "$WRONG_HEADERS"
fi
echo ""
# Summary
echo "======================================"
echo "Test Summary"
echo "======================================"
echo "Backend URL: $BACKEND_URL"
echo "Frontend URL: $FRONTEND_URL"
echo ""
echo "If all tests passed:"
echo " ✅ Your CORS configuration is working correctly"
echo ""
echo "If tests failed:"
echo " 1. Verify backend is running at $BACKEND_URL"
echo " 2. Check CORS_ALLOWED_ORIGINS environment variable"
echo " 3. Ensure it includes $FRONTEND_URL"
echo " 4. Restart backend after environment changes"
echo " 5. See CORS-GUIDE.md for detailed troubleshooting"
echo "======================================"