mirror of
https://github.com/johndoe6345789/snippet-pastebin.git
synced 2026-04-24 13:34:55 +00:00
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:
18
.env.example
18
.env.example
@@ -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
|
||||||
|
|||||||
@@ -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
383
CORS-CONFIG-SUMMARY.md
Normal 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
409
CORS-GUIDE.md
Normal 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
190
DEPLOYMENT-CHECKLIST.md
Normal 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
380
DEPLOYMENT.md
Normal 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
329
ENV-CONFIG.md
Normal 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)
|
||||||
10
README.md
10
README.md
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
372
backend/app.py
372
backend/app.py
@@ -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)
|
||||||
|
|||||||
4
backend/captain-definition
Normal file
4
backend/captain-definition
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"dockerfilePath": "./Dockerfile"
|
||||||
|
}
|
||||||
4
captain-definition
Normal file
4
captain-definition
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"dockerfilePath": "./Dockerfile"
|
||||||
|
}
|
||||||
35
docker-compose.production.yml
Normal file
35
docker-compose.production.yml
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
10
nginx.conf
10
nginx.conf
@@ -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
138
test-cors.sh
Normal 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 "======================================"
|
||||||
Reference in New Issue
Block a user