docs: consolidate and sanitize documentation for public release
**Changes Summary:** **Files Deleted (6 files):** - doc/HTMX-PRODUCTION-RECOMMENDATIONS.md (implementation notes) - doc/SEO-OPTIMIZATION-COMPLETE.md (implementation artifact) - doc/PROJECT-DOCUMENTATION-SUMMARY.md (meta-documentation) - doc/PROJECT_STATUS.md (internal roadmap) - doc/API-QUICK-REFERENCE.md (consolidated into API.md) - doc/API-PROTECTION.md (consolidated into API.md and SECURITY.md) **API Documentation Enhanced:** - Added Quick Reference section to API.md (from API-QUICK-REFERENCE.md) - Added Security & Protection section to API.md (from API-PROTECTION.md) - Updated Rate Limiting section to reflect actual implementation - Added Origin Checking documentation with examples **SECURITY.md Enhanced:** - Added comprehensive API Protection Features section - Updated Rate Limiting section (was marked "Not implemented", now shows it IS implemented) - Added Origin Checking configuration and examples - Added Combined Protection flow documentation - Added Testing Protection section with curl examples - Added Production Deployment Checklist - Added Troubleshooting section **Private Information Removed:** - README.md: Removed matomo.drolo.club and site ID references - PRIVACY.md: Replaced specific Matomo server with generic template - CUSTOMIZATION.md: Replaced Analytics Configuration with generic guide - All Matomo-specific details replaced with placeholders **Documentation Navigation:** - README.md: Enhanced Documentation section with organized categories - Getting Started (README, DEPLOYMENT, CUSTOMIZATION) - Technical Reference (ARCHITECTURE, API) - Policies & Standards (SECURITY, PRIVACY, CODE_OF_CONDUCT, CONTRIBUTING) - License **Broken Links Fixed:** - Removed reference to non-existent CHANGELOG.md in API.md - Fixed relative paths for cross-document references - Verified all internal documentation links **Result:** - Documentation reduced from 16 files to 10 core files (37.5% reduction) - No private information exposed (all Matomo details sanitized) - No implementation artifacts remaining - Clear, professional structure suitable for public instructive project - Comprehensive API and security documentation - All essential content preserved and enhanced This documentation now represents a professional, instructive open-source project suitable for public consumption and learning purposes.
This commit is contained in:
@@ -43,7 +43,7 @@ A professional, bilingual CV site with server-side PDF generation, HTMX interact
|
||||
- ✅ **JSON-Based Content** - Easy to update without touching code
|
||||
- ✅ **AI Development Section** - Showcases modern AI-assisted development skills
|
||||
- ✅ **Fast & Lightweight** - Go backend with chromedp for PDF generation
|
||||
- ✅ **Privacy-Friendly Analytics** - Self-hosted Matomo tracking (no third-party data sharing)
|
||||
- ✅ **Privacy-Friendly Analytics** - Self-hosted analytics (no third-party data sharing)
|
||||
- ✅ **Security Hardened** - CSP headers, XSS protection, origin validation, rate limiting
|
||||
- ✅ **Production Ready** - Systemd service, CI/CD workflows, deployment guides
|
||||
- ✅ **Developer Friendly** - Hot reload, clear code structure, comprehensive Makefile
|
||||
@@ -141,11 +141,24 @@ No code changes needed - just refresh browser!
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **[DEPLOYMENT.md](doc/DEPLOYMENT.md)** - Production deployment guides (VPS, cloud platforms, systemd)
|
||||
- **[CUSTOMIZATION.md](doc/CUSTOMIZATION.md)** - Complete guide to customizing this template for your CV
|
||||
- **[API.md](doc/API.md)** - HTTP endpoints documentation and HTMX integration
|
||||
- **[SECURITY.md](doc/SECURITY.md)** - Security policy, vulnerability reporting, deployment considerations
|
||||
- **[PRIVACY.md](doc/PRIVACY.md)** - Privacy policy and analytics disclosure
|
||||
This project includes comprehensive documentation organized by purpose:
|
||||
|
||||
### 📖 Getting Started
|
||||
- **[README.md](README.md)** - Project overview, quick start, and features (you are here)
|
||||
- **[DEPLOYMENT.md](doc/DEPLOYMENT.md)** - Production deployment guides for VPS and cloud platforms
|
||||
- **[CUSTOMIZATION.md](doc/CUSTOMIZATION.md)** - How to customize this CV template for your own use
|
||||
|
||||
### 🔧 Technical Reference
|
||||
- **[ARCHITECTURE.md](doc/ARCHITECTURE.md)** - System design, patterns, and technical decisions
|
||||
- **[API.md](doc/API.md)** - Complete HTTP API reference and HTMX integration
|
||||
|
||||
### 📋 Policies & Standards
|
||||
- **[SECURITY.md](doc/SECURITY.md)** - Security policy, vulnerability reporting, and best practices
|
||||
- **[PRIVACY.md](doc/PRIVACY.md)** - Privacy policy template and analytics guidance
|
||||
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** - Community standards (Contributor Covenant)
|
||||
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Contribution policy (personal project notice)
|
||||
|
||||
### 📄 License
|
||||
- **[LICENSE](LICENSE)** - MIT License
|
||||
|
||||
## 🚀 Deployment
|
||||
@@ -209,26 +222,14 @@ The [CUSTOMIZATION.md](doc/CUSTOMIZATION.md) guide includes:
|
||||
|
||||
## 🔒 Privacy & Analytics
|
||||
|
||||
This site uses **self-hosted Matomo analytics** to understand visitor behavior while respecting privacy.
|
||||
This site demonstrates self-hosted analytics implementation for privacy-conscious tracking.
|
||||
|
||||
**What's tracked:**
|
||||
- Page views and language changes (EN/ES)
|
||||
- Visitor country/city (approximate)
|
||||
- Browser type and referring site
|
||||
- Time on site and navigation patterns
|
||||
**Key features:**
|
||||
- Self-hosted analytics (no third-party data sharing)
|
||||
- Privacy-friendly (respects Do Not Track)
|
||||
- Fully configurable
|
||||
|
||||
**What's NOT tracked:**
|
||||
- Personal identifying information
|
||||
- Precise geolocation
|
||||
- Cross-site behavior
|
||||
- Any data is NOT shared with third parties
|
||||
|
||||
**Your privacy:**
|
||||
- All data stored on my own server (`matomo.drolo.club`)
|
||||
- Respects "Do Not Track" browser settings
|
||||
- You can disable cookies in browser settings
|
||||
|
||||
See **[PRIVACY.md](doc/PRIVACY.md)** for complete details and opt-out instructions.
|
||||
See **[PRIVACY.md](doc/PRIVACY.md)** for complete privacy policy template.
|
||||
|
||||
---
|
||||
|
||||
@@ -236,11 +237,13 @@ See **[PRIVACY.md](doc/PRIVACY.md)** for complete details and opt-out instructio
|
||||
|
||||
**This project is open-source and available for you to use!**
|
||||
|
||||
**If you use this as a template, you MUST change:**
|
||||
1. **Matomo Site ID** in `templates/index.html` (line 644): Change `setSiteId` from `'4'` to your own
|
||||
2. **Matomo Server URL** in `templates/index.html` (line 642): Change `https://matomo.drolo.club/` to your instance
|
||||
3. **CSP Headers** in `internal/middleware/security.go`: Update allowed domains for your Matomo server
|
||||
4. **OR remove Matomo entirely** if you don't want analytics (see [PRIVACY.md](doc/PRIVACY.md#for-developers-using-this-code))
|
||||
**If you use this as a template, you should:**
|
||||
1. Review the analytics implementation in `templates/index.html`
|
||||
2. Either configure your own analytics server or remove the tracking code
|
||||
3. Update CSP headers in `internal/middleware/security.go` accordingly
|
||||
4. Customize `PRIVACY.md` with your own privacy policy
|
||||
|
||||
See **[CUSTOMIZATION.md](doc/CUSTOMIZATION.md#analytics-configuration)** for detailed analytics configuration
|
||||
|
||||
**Other recommended changes:**
|
||||
- Update all personal information in `data/cv-en.json` and `data/cv-es.json`
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
# API Protection & Security
|
||||
|
||||
**Protection against external access and DDoS attacks on resource-intensive endpoints.**
|
||||
|
||||
---
|
||||
|
||||
## ✅ VERIFICATION STATUS
|
||||
|
||||
**Last Tested:** November 9, 2025
|
||||
**Status:** ✅ **ALL PROTECTION MECHANISMS VERIFIED WORKING**
|
||||
|
||||
### Verified Test Results
|
||||
|
||||
| Test | Expected | Actual | Status |
|
||||
|------|----------|--------|--------|
|
||||
| External referer (evil.com) | 403 Forbidden | 403 Forbidden | ✅ PASS |
|
||||
| Localhost referer | 200 OK | 200 OK | ✅ PASS |
|
||||
| Production domain referer | 200 OK | 200 OK | ✅ PASS |
|
||||
| External Origin header | 403 Forbidden | 403 Forbidden | ✅ PASS |
|
||||
| No referer (development) | 200 OK | 200 OK | ✅ PASS |
|
||||
| Rate limit (requests 1-3) | 200 OK | 200 OK | ✅ PASS |
|
||||
| Rate limit (request 4+) | 429 Too Many | 429 Too Many | ✅ PASS |
|
||||
|
||||
**Protection Layers:** Origin checking + Rate limiting both working correctly.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The CV website implements multiple layers of protection to prevent external sites from accessing the API and to protect against DDoS attacks on resource-intensive endpoints like PDF generation.
|
||||
|
||||
### Protection Layers
|
||||
|
||||
1. **Origin Checking** - Only allows requests from your domain
|
||||
2. **Rate Limiting** - Prevents abuse even from allowed origins
|
||||
3. **Production Restrictions** - Stricter rules in production environments
|
||||
|
||||
---
|
||||
|
||||
## 1. Origin Checking
|
||||
|
||||
**File:** `internal/middleware/security.go` (`OriginChecker` middleware)
|
||||
|
||||
### How It Works
|
||||
|
||||
The origin checker examines incoming HTTP requests and validates them against a whitelist of allowed domains. It checks two headers:
|
||||
|
||||
1. **Origin Header** - Set by browsers for CORS requests
|
||||
2. **Referer Header** - Set by browsers for navigation requests
|
||||
|
||||
### Configuration
|
||||
|
||||
**Environment Variable:** `ALLOWED_ORIGINS`
|
||||
|
||||
```bash
|
||||
# Development (default)
|
||||
ALLOWED_ORIGINS=
|
||||
|
||||
# Production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
| Environment | No Header | Localhost | Your Domain | External Domain |
|
||||
|-------------|-----------|-----------|-------------|-----------------|
|
||||
| **Development** | ✅ Allowed | ✅ Allowed | ✅ Allowed | ❌ Blocked |
|
||||
| **Production** | ❌ Blocked (PDF) | ✅ Allowed | ✅ Allowed | ❌ Blocked |
|
||||
|
||||
**Production PDF Endpoint:**
|
||||
- Requires `Origin` or `Referer` header
|
||||
- Blocks direct URL access without headers
|
||||
- Prevents bookmarking and external hotlinking
|
||||
|
||||
### Example Responses
|
||||
|
||||
**Allowed Request:**
|
||||
```bash
|
||||
curl -H "Referer: https://yourdomain.com/" http://localhost:1999/export/pdf?lang=en
|
||||
# Status: 200 OK
|
||||
# PDF file downloaded
|
||||
```
|
||||
|
||||
**Blocked Request (External Domain):**
|
||||
```bash
|
||||
curl -H "Referer: https://externaldomain.com/" http://localhost:1999/export/pdf?lang=en
|
||||
# Status: 403 Forbidden
|
||||
# Response: Forbidden: External access not allowed
|
||||
```
|
||||
|
||||
**Blocked Request (Production, No Headers):**
|
||||
```bash
|
||||
# In production with GO_ENV=production
|
||||
curl http://yourdomain.com/export/pdf?lang=en
|
||||
# Status: 403 Forbidden
|
||||
# Response: Forbidden: Direct access not allowed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Rate Limiting
|
||||
|
||||
**File:** `internal/middleware/security.go` (`RateLimiter`)
|
||||
|
||||
### How It Works
|
||||
|
||||
The rate limiter tracks requests per IP address and enforces limits on resource-intensive endpoints.
|
||||
|
||||
**Current Configuration:**
|
||||
- **Limit:** 3 requests per minute per IP
|
||||
- **Window:** 1 minute
|
||||
- **Applied to:** `/export/pdf` endpoint only
|
||||
|
||||
### Implementation
|
||||
|
||||
```go
|
||||
// Create rate limiter for PDF endpoint
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
|
||||
// Apply to PDF endpoint
|
||||
protectedPDFHandler := middleware.OriginChecker(
|
||||
pdfRateLimiter.Middleware(
|
||||
http.HandlerFunc(cvHandler.ExportPDF),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
### Behavior
|
||||
|
||||
| Requests | Status | Response |
|
||||
|----------|--------|----------|
|
||||
| 1st request | ✅ 200 OK | PDF generated |
|
||||
| 2nd request | ✅ 200 OK | PDF generated |
|
||||
| 3rd request | ✅ 200 OK | PDF generated |
|
||||
| 4th request (within 1 min) | ❌ 429 Too Many Requests | Rate limit exceeded |
|
||||
| After 1 minute | ✅ 200 OK | Counter reset |
|
||||
|
||||
### Headers
|
||||
|
||||
**Rate Limit Exceeded Response:**
|
||||
```
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Retry-After: 60
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Rate limit exceeded. Please try again later.
|
||||
```
|
||||
|
||||
### IP Detection
|
||||
|
||||
The rate limiter detects client IP from:
|
||||
1. `X-Forwarded-For` header (proxy/CDN)
|
||||
2. `X-Real-IP` header (alternative proxy header)
|
||||
3. `RemoteAddr` (direct connection)
|
||||
|
||||
**Supports reverse proxies:** Yes (Nginx, Cloudflare, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 3. Combined Protection
|
||||
|
||||
The PDF endpoint has **both** origin checking and rate limiting applied:
|
||||
|
||||
```
|
||||
Request → OriginChecker → RateLimiter → PDF Handler
|
||||
```
|
||||
|
||||
**Protection Flow:**
|
||||
|
||||
1. **Check Origin/Referer**
|
||||
- If external domain → 403 Forbidden
|
||||
- If production + no headers → 403 Forbidden
|
||||
- Otherwise, continue
|
||||
|
||||
2. **Check Rate Limit**
|
||||
- If > 3 requests/minute → 429 Too Many Requests
|
||||
- Otherwise, continue
|
||||
|
||||
3. **Generate PDF**
|
||||
- Process request normally
|
||||
|
||||
---
|
||||
|
||||
## 4. Configuration Examples
|
||||
|
||||
### Development Environment
|
||||
|
||||
```bash
|
||||
# .env
|
||||
PORT=1999
|
||||
HOST=localhost
|
||||
GO_ENV=development
|
||||
ALLOWED_ORIGINS=
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Allows `localhost` and `127.0.0.1`
|
||||
- Allows requests without headers
|
||||
- Rate limit: 3 PDF/min per IP
|
||||
|
||||
### Production Environment
|
||||
|
||||
```bash
|
||||
# .env
|
||||
PORT=1999
|
||||
HOST=0.0.0.0
|
||||
GO_ENV=production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Only allows `yourdomain.com` and `www.yourdomain.com`
|
||||
- Requires `Origin` or `Referer` header for PDF endpoint
|
||||
- Rate limit: 3 PDF/min per IP
|
||||
|
||||
### Multiple Domains
|
||||
|
||||
```bash
|
||||
# Support multiple domains (e.g., staging + production)
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com,staging.yourdomain.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing Protection
|
||||
|
||||
### Test Origin Checking
|
||||
|
||||
```bash
|
||||
# ✅ Allowed (localhost in development)
|
||||
curl http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ✅ Allowed (with referer)
|
||||
curl -H "Referer: http://localhost:1999/" http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ❌ Blocked (external referer)
|
||||
curl -H "Referer: https://evil.com/" http://localhost:1999/export/pdf?lang=en
|
||||
# Expected: 403 Forbidden
|
||||
```
|
||||
|
||||
### Test Rate Limiting
|
||||
|
||||
```bash
|
||||
# Generate 4 PDFs quickly
|
||||
for i in {1..4}; do
|
||||
echo "Request $i:"
|
||||
curl -w "Status: %{http_code}\n" -o /dev/null -s http://localhost:1999/export/pdf?lang=en
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Expected output:
|
||||
# Request 1: Status: 200
|
||||
# Request 2: Status: 200
|
||||
# Request 3: Status: 200
|
||||
# Request 4: Status: 429
|
||||
```
|
||||
|
||||
### Test Combined Protection
|
||||
|
||||
```bash
|
||||
# Should be blocked by origin checker before rate limiter
|
||||
for i in {1..5}; do
|
||||
curl -H "Referer: https://evil.com/" -w "Status: %{http_code}\n" -o /dev/null -s \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
done
|
||||
|
||||
# Expected: All requests get 403 (origin check fails immediately)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Monitoring & Logs
|
||||
|
||||
### Log Messages
|
||||
|
||||
**Origin Check Failure:**
|
||||
```
|
||||
# No specific log (returns 403 silently)
|
||||
# Check server logs for 403 responses
|
||||
```
|
||||
|
||||
**Rate Limit Exceeded:**
|
||||
```
|
||||
# No specific log (returns 429 silently)
|
||||
# Monitor for frequent 429 responses
|
||||
```
|
||||
|
||||
### Recommended Monitoring
|
||||
|
||||
1. **Track 403 responses** - Indicates potential attack attempts
|
||||
2. **Track 429 responses** - Indicates rate limiting in effect
|
||||
3. **Monitor PDF generation times** - Detect abuse patterns
|
||||
4. **Alert on sustained high request rates** - DDoS detection
|
||||
|
||||
---
|
||||
|
||||
## 7. Customization
|
||||
|
||||
### Adjust Rate Limits
|
||||
|
||||
**File:** `main.go`
|
||||
|
||||
```go
|
||||
// Current: 3 requests per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
|
||||
// More restrictive: 5 per hour
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)
|
||||
|
||||
// Less restrictive: 10 per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(10, 1*time.Minute)
|
||||
```
|
||||
|
||||
### Apply to Other Endpoints
|
||||
|
||||
```go
|
||||
// Protect /cv endpoint
|
||||
protectedCVHandler := middleware.OriginChecker(
|
||||
http.HandlerFunc(cvHandler.CVContent),
|
||||
)
|
||||
mux.Handle("/cv", protectedCVHandler)
|
||||
```
|
||||
|
||||
### Disable Origin Checking (Not Recommended)
|
||||
|
||||
```go
|
||||
// Apply only rate limiting (no origin check)
|
||||
mux.Handle("/export/pdf", pdfRateLimiter.Middleware(
|
||||
http.HandlerFunc(cvHandler.ExportPDF),
|
||||
))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Security Best Practices
|
||||
|
||||
### ✅ Recommended
|
||||
|
||||
1. **Set ALLOWED_ORIGINS in production** - Never run production without it
|
||||
2. **Use HTTPS** - Prevents header spoofing
|
||||
3. **Monitor 403/429 responses** - Detect attack patterns
|
||||
4. **Consider CloudFlare** - Additional DDoS protection layer
|
||||
5. **Log suspicious requests** - For forensic analysis
|
||||
|
||||
### ❌ Anti-Patterns
|
||||
|
||||
1. **Don't disable protection in production** - Always use origin checking
|
||||
2. **Don't set rate limits too high** - PDF generation is expensive
|
||||
3. **Don't trust IP addresses alone** - Use combined protection
|
||||
4. **Don't expose internal endpoints** - Keep admin routes private
|
||||
|
||||
---
|
||||
|
||||
## 9. Production Deployment Checklist
|
||||
|
||||
Before deploying to production:
|
||||
|
||||
- [ ] Set `GO_ENV=production` in environment
|
||||
- [ ] Configure `ALLOWED_ORIGINS` with your domain(s)
|
||||
- [ ] Test origin checking with external domain
|
||||
- [ ] Test rate limiting with rapid requests
|
||||
- [ ] Verify HTTPS is enabled (prevents header spoofing)
|
||||
- [ ] Set up monitoring for 403/429 responses
|
||||
- [ ] Configure log retention for security analysis
|
||||
- [ ] Test PDF generation under load
|
||||
- [ ] Verify reverse proxy headers (X-Forwarded-For)
|
||||
- [ ] Document allowed origins in runbook
|
||||
|
||||
---
|
||||
|
||||
## 10. Troubleshooting
|
||||
|
||||
### Problem: Legitimate users getting 403
|
||||
|
||||
**Cause:** ALLOWED_ORIGINS not configured correctly
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Ensure all your domains are listed
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
|
||||
# Check for typos (case-insensitive but must match exactly)
|
||||
```
|
||||
|
||||
### Problem: Rate limit too restrictive
|
||||
|
||||
**Cause:** Legitimate users hitting limit
|
||||
|
||||
**Solution:**
|
||||
```go
|
||||
// Increase limit or window in main.go
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Minute)
|
||||
```
|
||||
|
||||
### Problem: Behind reverse proxy, rate limit not working
|
||||
|
||||
**Cause:** IP detection failing
|
||||
|
||||
**Solution:**
|
||||
```nginx
|
||||
# Ensure Nginx passes correct headers
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
```
|
||||
|
||||
### Problem: Origin header not being sent
|
||||
|
||||
**Cause:** Browser doesn't send Origin for same-origin requests
|
||||
|
||||
**Solution:** This is normal. The middleware checks Referer as fallback.
|
||||
|
||||
---
|
||||
|
||||
## 11. Attack Scenarios & Mitigation
|
||||
|
||||
### Scenario 1: DDoS via PDF Generation
|
||||
|
||||
**Attack:** External site hotlinks to `/export/pdf`, triggering many PDF generations
|
||||
|
||||
**Mitigation:**
|
||||
1. ✅ Origin checker blocks external domains (403)
|
||||
2. ✅ Rate limiter prevents >3 requests/min per IP (429)
|
||||
3. ✅ Production mode requires headers (blocks direct access)
|
||||
|
||||
**Result:** Attack fails, server protected
|
||||
|
||||
### Scenario 2: Header Spoofing
|
||||
|
||||
**Attack:** Attacker spoofs `Referer` header to bypass origin check
|
||||
|
||||
**Mitigation:**
|
||||
1. ⚠️ HTTPS prevents header modification in transit
|
||||
2. ✅ Rate limiter still applies (3 req/min limit)
|
||||
3. ✅ IP-based tracking prevents distributed spoofing
|
||||
|
||||
**Result:** Individual attacker limited to 3 req/min
|
||||
|
||||
### Scenario 3: Distributed Attack
|
||||
|
||||
**Attack:** Botnet with many IPs, each generating PDFs
|
||||
|
||||
**Mitigation:**
|
||||
1. ✅ Each IP limited to 3 req/min
|
||||
2. ✅ Origin checker blocks if no valid referer
|
||||
3. 🔴 Consider CloudFlare for large-scale DDoS
|
||||
|
||||
**Result:** Slowed but not fully blocked (add CloudFlare)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Protection Enabled:** ✅ Origin Checking + Rate Limiting
|
||||
|
||||
**Endpoints Protected:**
|
||||
- `/export/pdf` - Full protection (origin + rate limit)
|
||||
|
||||
**Endpoints Unprotected:**
|
||||
- `/` - Public home page
|
||||
- `/cv` - Public CV content
|
||||
- `/health` - Public health check
|
||||
- `/static/*` - Public static files
|
||||
|
||||
**Configuration:** Environment-based via `ALLOWED_ORIGINS`
|
||||
|
||||
**Production Ready:** Yes (after setting ALLOWED_ORIGINS)
|
||||
|
||||
---
|
||||
|
||||
**For questions or to adjust protection levels, modify:**
|
||||
- `internal/middleware/security.go` - Origin checking and rate limiting logic
|
||||
- `main.go` - Apply protection to additional endpoints
|
||||
- `.env` - Configure ALLOWED_ORIGINS for your domain
|
||||
@@ -1,111 +0,0 @@
|
||||
# API Quick Reference
|
||||
|
||||
## Base URL
|
||||
```
|
||||
http://localhost:1999
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### 🏠 Home Page
|
||||
```bash
|
||||
GET /?lang={en|es}
|
||||
```
|
||||
Full HTML page with CV content.
|
||||
|
||||
### 📄 CV Content (HTMX)
|
||||
```bash
|
||||
GET /cv?lang={en|es}
|
||||
```
|
||||
HTML partial for HTMX swaps.
|
||||
|
||||
### 📥 PDF Export
|
||||
```bash
|
||||
GET /export/pdf?lang={en|es}
|
||||
```
|
||||
Downloads PDF resume (~1.8 MB, takes ~3 seconds).
|
||||
|
||||
### ❤️ Health Check
|
||||
```bash
|
||||
GET /health
|
||||
```
|
||||
Returns JSON: `{"status": "ok", "timestamp": "...", "version": "1.0.0"}`
|
||||
|
||||
### 🎨 Static Files
|
||||
```bash
|
||||
GET /static/{path}
|
||||
```
|
||||
Serves CSS, JS, images with cache headers.
|
||||
|
||||
## Quick Tests
|
||||
|
||||
### Test All Endpoints
|
||||
```bash
|
||||
# Health
|
||||
curl http://localhost:1999/health | jq
|
||||
|
||||
# Home (English)
|
||||
curl "http://localhost:1999/?lang=en"
|
||||
|
||||
# Home (Spanish)
|
||||
curl "http://localhost:1999/?lang=es"
|
||||
|
||||
# CV Content
|
||||
curl "http://localhost:1999/cv?lang=en"
|
||||
|
||||
# PDF Export
|
||||
curl -O -J "http://localhost:1999/export/pdf?lang=en"
|
||||
|
||||
# Static File
|
||||
curl -I http://localhost:1999/static/css/main.css
|
||||
```
|
||||
|
||||
### HTMX Language Switcher
|
||||
```html
|
||||
<button
|
||||
hx-get="/cv?lang=en"
|
||||
hx-target="#cv-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="/?lang=en">
|
||||
English
|
||||
</button>
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Meaning | Example |
|
||||
|------|---------|---------|
|
||||
| 200 | Success | All valid requests |
|
||||
| 400 | Bad Request | `?lang=invalid` |
|
||||
| 404 | Not Found | `/nonexistent` |
|
||||
| 500 | Server Error | Template/data/PDF error |
|
||||
|
||||
## Performance
|
||||
|
||||
- **Health**: <1ms
|
||||
- **HTML Pages**: 7-8ms
|
||||
- **Static Files**: <5ms
|
||||
- **PDF Export**: ~3 seconds
|
||||
|
||||
## Configuration (ENV)
|
||||
|
||||
```bash
|
||||
PORT=1999
|
||||
HOST=localhost
|
||||
GO_ENV=development
|
||||
READ_TIMEOUT=15
|
||||
WRITE_TIMEOUT=15
|
||||
```
|
||||
|
||||
## Security Headers
|
||||
|
||||
✅ Content-Security-Policy
|
||||
✅ X-Frame-Options: SAMEORIGIN
|
||||
✅ X-Content-Type-Options: nosniff
|
||||
✅ Referrer-Policy
|
||||
✅ Permissions-Policy
|
||||
✅ HSTS (production only)
|
||||
|
||||
## Need More Details?
|
||||
|
||||
See [API.md](API.md) for complete documentation.
|
||||
+231
-33
@@ -40,6 +40,114 @@ The server can be configured via environment variables:
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Quick access to common operations and endpoints.**
|
||||
|
||||
### Base URL
|
||||
```
|
||||
http://localhost:1999
|
||||
```
|
||||
|
||||
### All Endpoints
|
||||
|
||||
| Endpoint | Method | Description | Common Use |
|
||||
|----------|--------|-------------|------------|
|
||||
| `/?lang={en\|es}` | GET | Full HTML page with CV content | Initial page load |
|
||||
| `/cv?lang={en\|es}` | GET | HTML partial for HTMX swaps | Language switching |
|
||||
| `/export/pdf?lang={en\|es}` | GET | Download PDF resume | Export functionality |
|
||||
| `/health` | GET | Health check (JSON) | Monitoring |
|
||||
| `/static/{path}` | GET | Static files (CSS, JS, images) | Assets |
|
||||
|
||||
### Quick curl Examples
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:1999/health | jq
|
||||
|
||||
# English CV (full page)
|
||||
curl "http://localhost:1999/?lang=en"
|
||||
|
||||
# Spanish CV (full page)
|
||||
curl "http://localhost:1999/?lang=es"
|
||||
|
||||
# CV content partial (for HTMX)
|
||||
curl "http://localhost:1999/cv?lang=en"
|
||||
|
||||
# Export PDF
|
||||
curl -O -J "http://localhost:1999/export/pdf?lang=en"
|
||||
|
||||
# Static file with headers
|
||||
curl -I http://localhost:1999/static/css/main.css
|
||||
```
|
||||
|
||||
### HTMX Integration Pattern
|
||||
|
||||
```html
|
||||
<!-- Language switcher button -->
|
||||
<button
|
||||
hx-get="/cv?lang=en"
|
||||
hx-target="#cv-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="/?lang=en">
|
||||
🇬🇧 English
|
||||
</button>
|
||||
|
||||
<button
|
||||
hx-get="/cv?lang=es"
|
||||
hx-target="#cv-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="/?lang=es">
|
||||
🇪🇸 Español
|
||||
</button>
|
||||
|
||||
<!-- Content container -->
|
||||
<main id="cv-content">
|
||||
<!-- CV content will be swapped here -->
|
||||
</main>
|
||||
```
|
||||
|
||||
### Common Error Codes
|
||||
|
||||
| Code | Meaning | Common Cause |
|
||||
|------|---------|--------------|
|
||||
| 200 | Success | Request processed correctly |
|
||||
| 400 | Bad Request | Invalid `lang` parameter (not `en` or `es`) |
|
||||
| 403 | Forbidden | Origin check failed (PDF endpoint) |
|
||||
| 404 | Not Found | Invalid route or static file not found |
|
||||
| 429 | Too Many Requests | Rate limit exceeded (PDF endpoint) |
|
||||
| 500 | Server Error | Template error, data loading error, PDF generation failed |
|
||||
|
||||
### Performance Targets
|
||||
|
||||
| Endpoint | Target Response Time |
|
||||
|----------|---------------------|
|
||||
| `/health` | <1ms |
|
||||
| `/` and `/cv` | 7-8ms |
|
||||
| `/static/*` | <5ms |
|
||||
| `/export/pdf` | ~3 seconds |
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
```bash
|
||||
# Development
|
||||
PORT=1999
|
||||
HOST=localhost
|
||||
GO_ENV=development
|
||||
|
||||
# Production
|
||||
PORT=1999
|
||||
HOST=0.0.0.0
|
||||
GO_ENV=production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
### Need More Details?
|
||||
|
||||
For comprehensive documentation of each endpoint, request/response formats, and advanced usage, see the [Detailed Endpoint Documentation](#detailed-endpoint-documentation) below.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints Overview
|
||||
|
||||
| Method | Path | Description | HTMX Support |
|
||||
@@ -1001,39 +1109,125 @@ if lang != "en" && lang != "es" {
|
||||
**Protected Endpoints:**
|
||||
- `/export/pdf` - Full protection (origin checking + rate limiting)
|
||||
|
||||
**Configuration via Environment Variable:**
|
||||
```bash
|
||||
# Development (default)
|
||||
ALLOWED_ORIGINS=
|
||||
|
||||
# Production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
**How It Works:**
|
||||
1. Checks `Origin` header (CORS requests)
|
||||
2. Falls back to `Referer` header (navigation requests)
|
||||
3. Allows localhost in development
|
||||
4. Blocks external domains in production
|
||||
5. Requires headers in production for PDF endpoint
|
||||
|
||||
**Example Requests:**
|
||||
|
||||
```bash
|
||||
# ✅ Allowed (localhost in development)
|
||||
curl http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ✅ Allowed (valid referer)
|
||||
curl -H "Referer: http://localhost:1999/" \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ❌ Blocked (external referer)
|
||||
curl -H "Referer: https://evil.com/" \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
# Response: 403 Forbidden
|
||||
```
|
||||
|
||||
For more details on origin checking, see [SECURITY.md](SECURITY.md#origin-checking).
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
**Status:** ✅ **Implemented**
|
||||
|
||||
**Current Configuration:**
|
||||
- **Endpoint:** `/export/pdf`
|
||||
- **Limit:** 3 requests per minute per IP
|
||||
- **Window:** 1 minute (rolling)
|
||||
- **Response:** 429 Too Many Requests when exceeded
|
||||
|
||||
**Implementation:**
|
||||
limit_req zone=api burst=5;
|
||||
```go
|
||||
// Applied in main.go
|
||||
```
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
|
||||
protectedPDFHandler := middleware.OriginChecker(
|
||||
pdfRateLimiter.Middleware(
|
||||
http.HandlerFunc(cvHandler.ExportPDF),
|
||||
),
|
||||
**2. Go Middleware:**
|
||||
```go
|
||||
// Example rate limiter
|
||||
import "golang.org/x/time/rate"
|
||||
|
||||
func RateLimit(next http.Handler) http.Handler {
|
||||
limiter := rate.NewLimiter(10, 20) // 10 req/s, burst 20
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !limiter.Allow() {
|
||||
)
|
||||
```
|
||||
return
|
||||
}
|
||||
|
||||
**Behavior:**
|
||||
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Security Best Practices
|
||||
|
||||
✅ **Implemented:**
|
||||
- Security headers (CSP, X-Frame-Options, etc.)
|
||||
- HSTS in production
|
||||
- Input validation
|
||||
- Error message sanitization (internal errors hidden)
|
||||
| Requests | Status | Response |
|
||||
|----------|--------|----------|
|
||||
| 1st-3rd requests | ✅ 200 OK | PDF generated |
|
||||
| 4th+ request (within 1 min) | ❌ 429 Too Many Requests | Rate limit exceeded |
|
||||
| After 1 minute | ✅ 200 OK | Counter reset |
|
||||
|
||||
**Rate Limit Response:**
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Retry-After: 60
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
```
|
||||
|
||||
**IP Detection:**
|
||||
- Checks `X-Forwarded-For` (proxy/CDN)
|
||||
- Falls back to `X-Real-IP` (alternative proxy header)
|
||||
- Uses `RemoteAddr` (direct connection)
|
||||
- Works with Nginx reverse proxy
|
||||
|
||||
**Testing Rate Limit:**
|
||||
```bash
|
||||
# Generate 4 PDFs quickly to test rate limiting
|
||||
for i in {1..4}; do
|
||||
echo "Request $i:"
|
||||
curl -w "Status: %{http_code}\n" -o /dev/null -s \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Expected output:
|
||||
# Request 1: Status: 200
|
||||
# Request 2: Status: 200
|
||||
# Request 3: Status: 200
|
||||
# Request 4: Status: 429
|
||||
```
|
||||
|
||||
**Customizing Rate Limits:**
|
||||
|
||||
Edit `main.go` to adjust limits:
|
||||
|
||||
```go
|
||||
// More restrictive: 5 per hour
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)
|
||||
|
||||
// Less restrictive: 10 per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(10, 1*time.Minute)
|
||||
```
|
||||
|
||||
For comprehensive protection documentation, see [SECURITY.md](SECURITY.md#api-protection).
|
||||
|
||||
### Security Best Practices
|
||||
|
||||
✅ **Implemented:**
|
||||
- Security headers (CSP, X-Frame-Options, etc.)
|
||||
- HSTS in production
|
||||
- Input validation
|
||||
- Error message sanitization (internal errors hidden)
|
||||
- Timeouts on all operations
|
||||
- Graceful shutdown
|
||||
- Origin checking (prevents external hotlinking)
|
||||
- Rate limiting (PDF endpoint: 3 requests/min per IP)
|
||||
- IP-based tracking (supports reverse proxies)
|
||||
|
||||
@@ -1043,13 +1237,17 @@ func RateLimit(next http.Handler) http.Handler {
|
||||
- Implement request logging with IP addresses
|
||||
- Add monitoring and alerting for 403/429 responses
|
||||
- Consider CloudFlare for additional DDoS protection
|
||||
- Set up log retention for security analysis
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
**Current State:** Not implemented
|
||||
|
||||
|
||||
### Recommended Implementation
|
||||
|
||||
### Recommended Implementation
|
||||
|
||||
#### Per-Endpoint Limits
|
||||
|
||||
| Endpoint | Recommended Limit | Burst |
|
||||
|----------|-------------------|-------|
|
||||
@@ -1731,9 +1929,9 @@ go tool trace trace.out
|
||||
**Email:** [juan.a.moreno.rubio@gmail.com](mailto:juan.a.moreno.rubio@gmail.com)
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
**Last Updated:** November 9, 2025
|
||||
|
||||
**Last Updated:** November 9, 2025
|
||||
**API Version:** 1.0.0
|
||||
**Documentation Version:** 1.0.0
|
||||
### Support
|
||||
|
||||
|
||||
+52
-91
@@ -17,8 +17,9 @@
|
||||
- [Branding](#branding)
|
||||
- [Template Customization](#template-customization)
|
||||
- [Analytics Configuration](#analytics-configuration)
|
||||
- [Option 1: Use Your Own Matomo](#option-1-use-your-own-matomo-instance)
|
||||
- [Option 2: Remove Matomo](#option-2-remove-matomo-entirely)
|
||||
- [Option 1: Configure Your Own Analytics](#option-1-configure-your-own-analytics)
|
||||
- [Option 2: Remove Analytics Entirely](#option-2-remove-analytics-entirely)
|
||||
- [Option 3: Use Alternative Analytics Service](#option-3-use-alternative-analytics-service)
|
||||
- [Option 3: Use Alternative Analytics](#option-3-use-google-analytics-or-other-service)
|
||||
- [Advanced Customization](#advanced-customization)
|
||||
- [Testing Your Changes](#testing-your-changes)
|
||||
@@ -93,7 +94,7 @@ open http://localhost:1999
|
||||
4. Replace `education` section
|
||||
5. Update `skills` section
|
||||
6. Replace profile photo
|
||||
7. **Update Matomo analytics** (see [Analytics Configuration](#analytics-configuration) below)
|
||||
7. **Configure or remove analytics** (see [Analytics Configuration](#analytics-configuration) below)
|
||||
|
||||
---
|
||||
|
||||
@@ -1019,130 +1020,90 @@ tmpl := template.New("").Funcs(funcMap)
|
||||
|
||||
## Analytics Configuration
|
||||
|
||||
**CRITICAL:** If you use this template, you **MUST** update or remove the Matomo analytics configuration.
|
||||
This template includes a self-hosted analytics implementation as a learning example. You have three options:
|
||||
|
||||
### Option 1: Use Your Own Matomo Instance
|
||||
### Option 1: Configure Your Own Analytics
|
||||
|
||||
**Step 1:** Set up your own Matomo server
|
||||
- Install Matomo on your server or use a hosted service
|
||||
- Create a new website in Matomo dashboard
|
||||
- Note your Site ID and server URL
|
||||
If you want to use self-hosted analytics:
|
||||
|
||||
**Step 2:** Update tracking code in `templates/index.html` (around line 635-649)
|
||||
1. **Set up your analytics server** (Matomo, Plausible, or similar)
|
||||
|
||||
Find this section:
|
||||
```javascript
|
||||
<!-- Matomo -->
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://matomo.drolo.club/"; // ← CHANGE THIS
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '4']); // ← CHANGE THIS
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
```
|
||||
2. **Update tracking code** in `templates/index.html`:
|
||||
```javascript
|
||||
// Find this section near the end of the file
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
|
||||
**Change:**
|
||||
1. **Line 642:** Replace `https://matomo.drolo.club/` with your Matomo server URL
|
||||
2. **Line 644:** Replace `'4'` with your Site ID from Matomo dashboard
|
||||
var u="https://YOUR-ANALYTICS-SERVER.COM/"; // Replace this
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', 'YOUR-SITE-ID']); // Replace this
|
||||
```
|
||||
|
||||
**Step 3:** Update Content Security Policy in `internal/middleware/security.go` (lines 33, 37)
|
||||
3. **Update CSP headers** in `internal/middleware/security.go`:
|
||||
```go
|
||||
// Find the CSP policy and update these directives:
|
||||
"script-src 'self' 'unsafe-inline' https://YOUR-ANALYTICS-SERVER.COM; " +
|
||||
"connect-src 'self' https://YOUR-ANALYTICS-SERVER.COM; "
|
||||
```
|
||||
|
||||
Find and update these lines:
|
||||
```go
|
||||
// Line 33: Allow your Matomo domain for scripts
|
||||
"script-src 'self' 'unsafe-inline' https://unpkg.com https://code.iconify.design https://YOUR-MATOMO-DOMAIN.com; " +
|
||||
4. **Update PRIVACY.md** with your own privacy policy details
|
||||
|
||||
// Line 37: Allow your Matomo domain for API calls
|
||||
"connect-src 'self' https://api.iconify.design https://YOUR-MATOMO-DOMAIN.com; " +
|
||||
```
|
||||
|
||||
Replace `https://matomo.drolo.club` with your Matomo domain.
|
||||
|
||||
**Step 4:** Create your own privacy policy
|
||||
- Copy `PRIVACY.md` and update with your contact information
|
||||
- Update cookie disclosure with your Matomo server details
|
||||
- Ensure compliance with GDPR/privacy laws in your jurisdiction
|
||||
|
||||
### Option 2: Remove Matomo Entirely
|
||||
### Option 2: Remove Analytics Entirely
|
||||
|
||||
If you don't want analytics:
|
||||
|
||||
**Step 1:** Remove tracking code from `templates/index.html`
|
||||
1. **Remove tracking code** from `templates/index.html`:
|
||||
- Delete the entire `<!-- Matomo Analytics -->` section (usually at the end of `<body>`)
|
||||
|
||||
Delete lines 623-649 (the entire Matomo section):
|
||||
```javascript
|
||||
// Delete this entire block:
|
||||
// Track HTMX navigation events with Matomo
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) { ... });
|
||||
2. **Simplify CSP headers** in `internal/middleware/security.go`:
|
||||
```go
|
||||
// Remove analytics domains from:
|
||||
"script-src 'self' 'unsafe-inline'; " + // Remove analytics domain
|
||||
"connect-src 'self'; " // Remove analytics domain
|
||||
```
|
||||
|
||||
<!-- Matomo -->
|
||||
<script> ... </script>
|
||||
<!-- End Matomo Code -->
|
||||
```
|
||||
3. **Update PRIVACY.md**:
|
||||
- Simplify to state no tracking is used
|
||||
- Or remove the file if not needed
|
||||
|
||||
**Step 2:** Remove Matomo from CSP headers in `internal/middleware/security.go`
|
||||
### Option 3: Use Alternative Analytics Service
|
||||
|
||||
Remove `https://matomo.drolo.club` from lines 33 and 37:
|
||||
```go
|
||||
// Before:
|
||||
"script-src 'self' 'unsafe-inline' https://unpkg.com https://code.iconify.design https://matomo.drolo.club; " +
|
||||
If you prefer Google Analytics, Plausible Cloud, or another service:
|
||||
|
||||
// After:
|
||||
"script-src 'self' 'unsafe-inline' https://unpkg.com https://code.iconify.design; " +
|
||||
```
|
||||
|
||||
**Step 3:** Update or remove `PRIVACY.md`
|
||||
- Remove analytics section
|
||||
- Keep only essential privacy information
|
||||
|
||||
### Option 3: Use Google Analytics or Other Service
|
||||
|
||||
If you prefer Google Analytics, Plausible, or another service:
|
||||
|
||||
1. **Remove Matomo code** (see Option 2 above)
|
||||
2. **Add your analytics provider's code** in the same location
|
||||
3. **Update CSP headers** to allow your analytics domain
|
||||
4. **Update PRIVACY.md** with your analytics provider's details
|
||||
5. **Ensure compliance** with privacy regulations (GDPR, CCPA, etc.)
|
||||
1. **Replace tracking code** in `templates/index.html` with your provider's script
|
||||
2. **Update CSP headers** with your provider's domains
|
||||
3. **Update PRIVACY.md** according to your provider's data handling
|
||||
4. **Note**: External services may require additional privacy disclosures (GDPR, CCPA)
|
||||
|
||||
### Testing Analytics
|
||||
|
||||
After configuration:
|
||||
|
||||
```bash
|
||||
# 1. Build and run
|
||||
go build -o cv-server . && ./cv-server
|
||||
# Start development server
|
||||
make dev
|
||||
|
||||
# 2. Open browser with developer tools
|
||||
# Visit site in browser
|
||||
open http://localhost:1999
|
||||
|
||||
# 3. Check Console for errors
|
||||
# - Should see Matomo requests if configured
|
||||
# - Should see no errors about blocked scripts
|
||||
|
||||
# 4. Verify in your analytics dashboard
|
||||
# - Real-time visitors should show your session
|
||||
# - Language switches should track as pageviews
|
||||
# Check browser console for errors
|
||||
# Verify analytics requests in Network tab
|
||||
```
|
||||
|
||||
**Security Note:** Always use HTTPS in production to protect analytics data in transit.
|
||||
|
||||
### Privacy Compliance
|
||||
|
||||
**Important legal considerations:**
|
||||
|
||||
- ✅ Add cookie banner if required in your jurisdiction (EU requires consent)
|
||||
- ✅ Add cookie consent banner if required in your jurisdiction
|
||||
- ✅ Create privacy policy explaining data collection
|
||||
- ✅ Provide opt-out mechanism
|
||||
- ✅ Comply with GDPR, CCPA, or local privacy laws
|
||||
- ✅ Update privacy policy when changing analytics providers
|
||||
|
||||
**See [PRIVACY.md](PRIVACY.md) for template privacy policy.**
|
||||
**See [PRIVACY.md](PRIVACY.md) for privacy policy template.**
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,884 +0,0 @@
|
||||
# HTMX CV Site - Production Readiness Review
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Current Status:** 85% Production Ready
|
||||
**Performance:** Exceptional (0.8-1ms response times, well below 85-120ms target)
|
||||
**Core Implementation:** Solid HTMX patterns, clean architecture
|
||||
**Priority Focus:** Accessibility, Error Handling, SEO, Security
|
||||
|
||||
---
|
||||
|
||||
## 1. HTMX Implementation Analysis
|
||||
|
||||
### ✅ **Strengths**
|
||||
|
||||
1. **Excellent Performance**
|
||||
- Initial page load: 0.8ms
|
||||
- HTMX partial swap: 1.0ms
|
||||
- Well below recommended 85-120ms target
|
||||
- Go backend provides exceptional speed
|
||||
|
||||
2. **Clean HTMX Patterns**
|
||||
- Proper use of `hx-get` for language switching
|
||||
- Targeted swaps with `hx-target="#cv-content"`
|
||||
- Loading indicators with `hx-indicator`
|
||||
- Locality of behavior maintained
|
||||
|
||||
3. **Good Progressive Enhancement**
|
||||
- Functional without JavaScript (direct URL access works)
|
||||
- Links are actual HTTP GET requests
|
||||
- No JavaScript frameworks required
|
||||
|
||||
### ⚠️ **Critical Issues to Address**
|
||||
|
||||
#### **1.1 Missing Browser History Management**
|
||||
|
||||
**Problem:** Language changes don't update browser URL
|
||||
**Impact:** Back button doesn't work, bookmarks don't preserve language
|
||||
**Priority:** HIGH
|
||||
|
||||
**Solution:**
|
||||
```html
|
||||
<button
|
||||
class="lang-btn {{if eq .Lang "en"}}active{{end}}"
|
||||
hx-get="/cv?lang=en"
|
||||
hx-target="#cv-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="/?lang=en" <!-- ADD THIS -->
|
||||
hx-indicator="#loading">
|
||||
🇬🇧 English
|
||||
</button>
|
||||
```
|
||||
|
||||
#### **1.2 No Error Handling**
|
||||
|
||||
**Problem:** Failed HTMX requests show no feedback to users
|
||||
**Impact:** Poor UX when network fails or server errors
|
||||
**Priority:** HIGH
|
||||
|
||||
**Solution:** Add global error handler
|
||||
```javascript
|
||||
document.body.addEventListener('htmx:responseError', function(evt) {
|
||||
// Show user-friendly error message
|
||||
showErrorToast('Failed to load content. Please try again.');
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:timeout', function(evt) {
|
||||
showErrorToast('Request timed out. Please check your connection.');
|
||||
});
|
||||
```
|
||||
|
||||
#### **1.3 Missing ARIA Live Regions**
|
||||
|
||||
**Problem:** Screen readers don't announce dynamic content changes
|
||||
**Impact:** Accessibility violation (WCAG 2.1 Level A)
|
||||
**Priority:** HIGH
|
||||
|
||||
**Solution:**
|
||||
```html
|
||||
<main id="cv-content"
|
||||
class="cv-paper"
|
||||
role="main"
|
||||
aria-live="polite"
|
||||
aria-atomic="false"> <!-- ADD THESE -->
|
||||
{{template "cv-content.html" .}}
|
||||
</main>
|
||||
```
|
||||
|
||||
#### **1.4 No Transition Effects**
|
||||
|
||||
**Problem:** Instant swaps feel jarring
|
||||
**Impact:** Poor UX, no visual feedback during changes
|
||||
**Priority:** MEDIUM
|
||||
|
||||
**Solution:**
|
||||
```html
|
||||
hx-swap="innerHTML swap:200ms settle:200ms"
|
||||
```
|
||||
|
||||
**CSS:**
|
||||
```css
|
||||
.cv-paper {
|
||||
transition: opacity 200ms;
|
||||
}
|
||||
|
||||
.cv-paper.htmx-swapping {
|
||||
opacity: 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### **1.5 No Request Timeout Configuration**
|
||||
|
||||
**Problem:** Requests wait indefinitely on slow connections
|
||||
**Impact:** Hanging UI, poor UX
|
||||
**Priority:** MEDIUM
|
||||
|
||||
**Solution:**
|
||||
```html
|
||||
<meta name="htmx-config" content='{"timeout":5000}'>
|
||||
```
|
||||
|
||||
#### **1.6 Language Preference Not Persisted**
|
||||
|
||||
**Problem:** Users must reselect language on each visit
|
||||
**Impact:** Inconvenience for repeat visitors
|
||||
**Priority:** LOW
|
||||
|
||||
**Solution:**
|
||||
```javascript
|
||||
// Save preference on language change
|
||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||
const url = new URL(evt.detail.xhr.responseURL);
|
||||
const lang = url.searchParams.get('lang');
|
||||
if (lang) localStorage.setItem('cv-lang', lang);
|
||||
});
|
||||
|
||||
// Load saved preference on page load
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
const savedLang = localStorage.getItem('cv-lang');
|
||||
if (savedLang) {
|
||||
// Trigger HTMX request for saved language
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Accessibility (WCAG 2.1 Level AA)
|
||||
|
||||
### ⚠️ **Critical Issues**
|
||||
|
||||
#### **2.1 Missing ARIA Attributes**
|
||||
|
||||
**Current State:** Minimal ARIA usage
|
||||
**Required for WCAG 2.1:**
|
||||
|
||||
```html
|
||||
<!-- Language toggle -->
|
||||
<div class="language-toggle" role="group" aria-label="Language selection">
|
||||
<button
|
||||
class="lang-btn"
|
||||
aria-label="Switch to English"
|
||||
aria-pressed="true|false"
|
||||
aria-busy="true|false"> <!-- During loading -->
|
||||
🇬🇧 English
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<span id="loading"
|
||||
class="htmx-indicator"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label="Loading">
|
||||
<span class="loader" aria-hidden="true"></span>
|
||||
<span class="sr-only">Loading...</span> <!-- Screen reader text -->
|
||||
</span>
|
||||
|
||||
<!-- Error messages -->
|
||||
<div role="alert" aria-live="assertive">
|
||||
<!-- Error content -->
|
||||
</div>
|
||||
```
|
||||
|
||||
#### **2.2 Missing Focus Management**
|
||||
|
||||
**Problem:** Focus doesn't move to updated content
|
||||
**Solution:** Add focus management after swap
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.id === 'cv-content') {
|
||||
// Focus on main heading
|
||||
const heading = evt.detail.target.querySelector('h1');
|
||||
if (heading) heading.focus();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### **2.3 Insufficient Keyboard Navigation**
|
||||
|
||||
**Recommendations:**
|
||||
- Add keyboard shortcuts (Ctrl+E for English, Ctrl+S for Spanish)
|
||||
- Ensure all interactive elements are keyboard accessible
|
||||
- Add skip-to-content link
|
||||
|
||||
```html
|
||||
<a href="#cv-content" class="skip-link">Skip to content</a>
|
||||
```
|
||||
|
||||
```css
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 0;
|
||||
background: var(--accent-blue);
|
||||
color: white;
|
||||
padding: 8px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
top: 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### **2.4 Color Contrast Issues**
|
||||
|
||||
**Check Required:**
|
||||
- `.text-light` (#6a6a6a) on white might not meet WCAG AA (4.5:1 ratio)
|
||||
- Test all color combinations with contrast checker
|
||||
|
||||
**Tool:** https://webaim.org/resources/contrastchecker/
|
||||
|
||||
---
|
||||
|
||||
## 3. SEO Optimization
|
||||
|
||||
### ⚠️ **Missing SEO Elements**
|
||||
|
||||
#### **3.1 Missing Meta Tags**
|
||||
|
||||
**Add to `<head>`:**
|
||||
```html
|
||||
<!-- Essential SEO -->
|
||||
<meta name="author" content="{{.CV.Personal.Name}}">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="{{.CV.Personal.Website}}">
|
||||
|
||||
<!-- Open Graph (Social Media) -->
|
||||
<meta property="og:title" content="{{.CV.Personal.Name}} - Curriculum Vitae">
|
||||
<meta property="og:description" content="{{.CV.Personal.Title}}">
|
||||
<meta property="og:type" content="profile">
|
||||
<meta property="og:url" content="{{.CV.Personal.Website}}">
|
||||
<meta property="og:image" content="{{.CV.Personal.Website}}/static/og-image.jpg">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="{{.CV.Personal.Name}}">
|
||||
<meta name="twitter:description" content="{{.CV.Personal.Title}}">
|
||||
|
||||
<!-- Professional Profile -->
|
||||
<meta property="profile:first_name" content="Juan Andrés">
|
||||
<meta property="profile:last_name" content="Moreno Rubio">
|
||||
<meta property="profile:username" content="txeo">
|
||||
```
|
||||
|
||||
#### **3.2 Missing Structured Data (JSON-LD)**
|
||||
|
||||
**Add before `</head>`:**
|
||||
```html
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "{{.CV.Personal.Name}}",
|
||||
"jobTitle": "{{.CV.Personal.Title}}",
|
||||
"url": "{{.CV.Personal.Website}}",
|
||||
"sameAs": [
|
||||
"{{.CV.Personal.LinkedIn}}",
|
||||
"{{.CV.Personal.GitHub}}"
|
||||
],
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": "{{.CV.Personal.Location}}"
|
||||
},
|
||||
"email": "{{.CV.Personal.Email}}",
|
||||
"telephone": "{{.CV.Personal.Phone}}"
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3.3 Missing Sitemap**
|
||||
|
||||
**Create `/sitemap.xml`:**
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://yoursite.com/?lang=en</loc>
|
||||
<lastmod>2025-10-18</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://yoursite.com/?lang=es</loc>
|
||||
<lastmod>2025-10-18</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
#### **3.4 Missing robots.txt**
|
||||
|
||||
**Create `/static/robots.txt`:**
|
||||
```
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://yoursite.com/sitemap.xml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Security Enhancements
|
||||
|
||||
### ⚠️ **Missing Security Headers**
|
||||
|
||||
#### **4.1 Add Security Middleware in Go**
|
||||
|
||||
**Create `middleware/security.go`:**
|
||||
```go
|
||||
func SecurityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Prevent clickjacking
|
||||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
|
||||
// Content Security Policy
|
||||
w.Header().Set("Content-Security-Policy",
|
||||
"default-src 'self'; "+
|
||||
"script-src 'self' 'unsafe-inline' https://unpkg.com; "+
|
||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "+
|
||||
"font-src 'self' https://fonts.gstatic.com; "+
|
||||
"img-src 'self' data:; "+
|
||||
"connect-src 'self'")
|
||||
|
||||
// Referrer Policy
|
||||
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
|
||||
// Permissions Policy
|
||||
w.Header().Set("Permissions-Policy",
|
||||
"geolocation=(), microphone=(), camera=()")
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **4.2 Add SRI (Subresource Integrity) for HTMX**
|
||||
|
||||
**Update HTMX script tag:**
|
||||
```html
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"
|
||||
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
|
||||
crossorigin="anonymous"></script>
|
||||
```
|
||||
|
||||
#### **4.3 Rate Limiting**
|
||||
|
||||
**Add to Go middleware:**
|
||||
```go
|
||||
// Simple rate limiter (use golang.org/x/time/rate in production)
|
||||
func RateLimit(next http.Handler) http.Handler {
|
||||
limiter := rate.NewLimiter(10, 20) // 10 requests/sec, burst of 20
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !limiter.Allow() {
|
||||
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Performance Optimizations
|
||||
|
||||
### ✅ **Already Excellent**
|
||||
|
||||
- Sub-millisecond response times
|
||||
- Minimal JavaScript
|
||||
- Clean HTML structure
|
||||
|
||||
### 🔧 **Additional Improvements**
|
||||
|
||||
#### **5.1 Add Resource Hints**
|
||||
|
||||
```html
|
||||
<head>
|
||||
<!-- Preconnect to external domains -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preconnect" href="https://unpkg.com">
|
||||
|
||||
<!-- DNS prefetch -->
|
||||
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
|
||||
</head>
|
||||
```
|
||||
|
||||
#### **5.2 Add Cache Control Headers**
|
||||
|
||||
**In Go handler:**
|
||||
```go
|
||||
// Static files
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
|
||||
// HTML pages
|
||||
w.Header().Set("Cache-Control", "public, max-age=3600, must-revalidate")
|
||||
|
||||
// HTMX partials
|
||||
w.Header().Set("Cache-Control", "private, max-age=300")
|
||||
```
|
||||
|
||||
#### **5.3 Compress Responses**
|
||||
|
||||
```go
|
||||
import "compress/gzip"
|
||||
|
||||
func GzipHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
|
||||
gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
next.ServeHTTP(gzw, r)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **5.4 Optimize Font Loading**
|
||||
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
media="print"
|
||||
onload="this.media='all'">
|
||||
<noscript>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet">
|
||||
</noscript>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Enhanced User Experience
|
||||
|
||||
### 🔧 **Recommended Enhancements**
|
||||
|
||||
#### **6.1 Add Smooth Scroll to Top**
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.id === 'cv-content') {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### **6.2 Add PDF Download with Custom Filename**
|
||||
|
||||
**Update Go handler:**
|
||||
```go
|
||||
func handlePDFExport(w http.ResponseWriter, r *http.Request) {
|
||||
lang := r.URL.Query().Get("lang")
|
||||
filename := fmt.Sprintf("CV-%s-%s.pdf", "Juan-Andres-Moreno", lang)
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
|
||||
// Redirect to print view
|
||||
http.Redirect(w, r, fmt.Sprintf("/?lang=%s&print=true", lang), http.StatusSeeOther)
|
||||
}
|
||||
```
|
||||
|
||||
#### **6.3 Add Keyboard Shortcuts**
|
||||
|
||||
```javascript
|
||||
document.addEventListener('keydown', function(evt) {
|
||||
// Ctrl/Cmd + P for print
|
||||
if ((evt.ctrlKey || evt.metaKey) && evt.key === 'p') {
|
||||
evt.preventDefault();
|
||||
window.print();
|
||||
}
|
||||
|
||||
// Ctrl/Cmd + E for English
|
||||
if ((evt.ctrlKey || evt.metaKey) && evt.key === 'e') {
|
||||
evt.preventDefault();
|
||||
document.querySelector('[hx-get="/cv?lang=en"]').click();
|
||||
}
|
||||
|
||||
// Ctrl/Cmd + Shift + S for Spanish
|
||||
if ((evt.ctrlKey || evt.metaKey) && evt.shiftKey && evt.key === 's') {
|
||||
evt.preventDefault();
|
||||
document.querySelector('[hx-get="/cv?lang=es"]').click();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### **6.4 Add Loading Skeleton**
|
||||
|
||||
**During HTMX swap, show skeleton:**
|
||||
```css
|
||||
.cv-content-loading {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Testing & Monitoring
|
||||
|
||||
### 🧪 **Testing Checklist**
|
||||
|
||||
#### **7.1 Functional Testing**
|
||||
|
||||
- [ ] Language switching works without page reload
|
||||
- [ ] Browser back/forward buttons work correctly
|
||||
- [ ] Bookmarks preserve language selection
|
||||
- [ ] PDF export generates correct filename
|
||||
- [ ] All links are functional
|
||||
- [ ] Form validation (if any future forms)
|
||||
|
||||
#### **7.2 Accessibility Testing**
|
||||
|
||||
**Tools:**
|
||||
- [ ] WAVE Browser Extension
|
||||
- [ ] axe DevTools
|
||||
- [ ] Lighthouse Accessibility Audit (target: 100)
|
||||
- [ ] Screen reader testing (NVDA, JAWS, VoiceOver)
|
||||
- [ ] Keyboard-only navigation
|
||||
|
||||
**Checklist:**
|
||||
- [ ] All images have alt text
|
||||
- [ ] Form labels are associated
|
||||
- [ ] Color contrast meets WCAG AA (4.5:1)
|
||||
- [ ] Focus indicators are visible
|
||||
- [ ] ARIA attributes are correct
|
||||
- [ ] Headings are hierarchical (h1 → h2 → h3)
|
||||
|
||||
#### **7.3 Performance Testing**
|
||||
|
||||
**Tools:**
|
||||
- [ ] Lighthouse Performance (target: 95+)
|
||||
- [ ] WebPageTest
|
||||
- [ ] Chrome DevTools Network tab
|
||||
|
||||
**Metrics:**
|
||||
- [ ] First Contentful Paint (FCP): <1.8s
|
||||
- [ ] Largest Contentful Paint (LCP): <2.5s
|
||||
- [ ] First Input Delay (FID): <100ms
|
||||
- [ ] Cumulative Layout Shift (CLS): <0.1
|
||||
- [ ] Time to Interactive (TTI): <3.8s
|
||||
|
||||
#### **7.4 Cross-Browser Testing**
|
||||
|
||||
- [ ] Chrome (latest)
|
||||
- [ ] Firefox (latest)
|
||||
- [ ] Safari (latest)
|
||||
- [ ] Edge (latest)
|
||||
- [ ] Mobile Safari (iOS)
|
||||
- [ ] Chrome Mobile (Android)
|
||||
|
||||
#### **7.5 Security Testing**
|
||||
|
||||
**Tools:**
|
||||
- [ ] Mozilla Observatory
|
||||
- [ ] Security Headers (securityheaders.com)
|
||||
- [ ] OWASP ZAP
|
||||
|
||||
**Checklist:**
|
||||
- [ ] HTTPS enforced
|
||||
- [ ] Security headers present
|
||||
- [ ] No XSS vulnerabilities
|
||||
- [ ] No SQL injection (if database used)
|
||||
- [ ] CSRF protection (if forms added)
|
||||
|
||||
---
|
||||
|
||||
## 8. Production Deployment Checklist
|
||||
|
||||
### 📦 **Pre-Deployment**
|
||||
|
||||
#### **8.1 Code Quality**
|
||||
|
||||
- [ ] All console.log statements removed
|
||||
- [ ] Error handling implemented
|
||||
- [ ] Code comments for complex logic
|
||||
- [ ] No hardcoded credentials
|
||||
- [ ] Environment variables configured
|
||||
|
||||
#### **8.2 Build Process**
|
||||
|
||||
```bash
|
||||
# Minify CSS
|
||||
npm install -g csso-cli
|
||||
csso static/css/main.css -o static/css/main.min.css
|
||||
|
||||
# Minify JavaScript (if custom JS added)
|
||||
npm install -g terser
|
||||
terser static/js/main.js -o static/js/main.min.js -c -m
|
||||
```
|
||||
|
||||
#### **8.3 Environment Configuration**
|
||||
|
||||
**Create `.env.production`:**
|
||||
```env
|
||||
GO_ENV=production
|
||||
PORT=1999
|
||||
HOST=0.0.0.0
|
||||
ALLOWED_ORIGINS=https://yoursite.com
|
||||
CACHE_CONTROL_MAX_AGE=86400
|
||||
```
|
||||
|
||||
#### **8.4 Monitoring Setup**
|
||||
|
||||
**Add health check endpoint:**
|
||||
```go
|
||||
func handleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "healthy",
|
||||
"version": "1.0.0",
|
||||
"timestamp": time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Add logging:**
|
||||
```go
|
||||
import "log/slog"
|
||||
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
logger.Info("Request received", "path", r.URL.Path, "method", r.Method)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Priority Implementation Plan
|
||||
|
||||
### Phase 1: Critical (Week 1)
|
||||
|
||||
1. **Accessibility**
|
||||
- Add ARIA attributes (4 hours)
|
||||
- Implement keyboard navigation (2 hours)
|
||||
- Test with screen readers (2 hours)
|
||||
|
||||
2. **Error Handling**
|
||||
- Global HTMX error handler (2 hours)
|
||||
- Error toast component (2 hours)
|
||||
- Timeout configuration (1 hour)
|
||||
|
||||
3. **Browser History**
|
||||
- Add `hx-push-url` (1 hour)
|
||||
- Test back/forward navigation (1 hour)
|
||||
|
||||
**Total:** ~15 hours
|
||||
|
||||
### Phase 2: Important (Week 2)
|
||||
|
||||
1. **SEO**
|
||||
- Meta tags and Open Graph (2 hours)
|
||||
- Structured data (2 hours)
|
||||
- Sitemap and robots.txt (1 hour)
|
||||
|
||||
2. **Security**
|
||||
- Security headers middleware (2 hours)
|
||||
- SRI for external scripts (1 hour)
|
||||
- Rate limiting (2 hours)
|
||||
|
||||
3. **UX Enhancements**
|
||||
- Transition effects (2 hours)
|
||||
- Language preference storage (2 hours)
|
||||
- Keyboard shortcuts (1 hour)
|
||||
|
||||
**Total:** ~15 hours
|
||||
|
||||
### Phase 3: Nice-to-Have (Week 3)
|
||||
|
||||
1. **Performance**
|
||||
- Resource hints (1 hour)
|
||||
- Gzip compression (2 hours)
|
||||
- Font optimization (1 hour)
|
||||
|
||||
2. **Testing**
|
||||
- Automated accessibility tests (4 hours)
|
||||
- Performance testing (2 hours)
|
||||
- Cross-browser testing (4 hours)
|
||||
|
||||
**Total:** ~14 hours
|
||||
|
||||
---
|
||||
|
||||
## 10. Files to Update/Create
|
||||
|
||||
### Update Existing Files
|
||||
|
||||
1. **`/Users/txeo/Git/yo/cv/templates/index.html`**
|
||||
- Add ARIA attributes
|
||||
- Add `hx-push-url`
|
||||
- Add error toast HTML
|
||||
- Add meta tags
|
||||
|
||||
2. **`/Users/txeo/Git/yo/cv/static/css/main.css`**
|
||||
- Add transition effects
|
||||
- Add error toast styles
|
||||
- Add focus styles
|
||||
- Add reduced motion support
|
||||
|
||||
3. **`/Users/txeo/Git/yo/cv/main.go`**
|
||||
- Add security headers
|
||||
- Add rate limiting
|
||||
- Add gzip compression
|
||||
- Add health check endpoint
|
||||
|
||||
### Create New Files
|
||||
|
||||
1. **`/Users/txeo/Git/yo/cv/static/js/htmx-enhancements.js`** (optional)
|
||||
- Error handling
|
||||
- Keyboard shortcuts
|
||||
- Language preference storage
|
||||
- Analytics events
|
||||
|
||||
2. **`/Users/txeo/Git/yo/cv/static/robots.txt`**
|
||||
- Search engine directives
|
||||
|
||||
3. **`/Users/txeo/Git/yo/cv/sitemap.xml`**
|
||||
- Site structure for SEO
|
||||
|
||||
4. **`/Users/txeo/Git/yo/cv/middleware/security.go`**
|
||||
- Security headers
|
||||
- Rate limiting
|
||||
|
||||
---
|
||||
|
||||
## 11. Enhanced Templates (Ready to Use)
|
||||
|
||||
I've created two enhanced template files for you:
|
||||
|
||||
1. **`/Users/txeo/Git/yo/cv/templates/index-improved.html`**
|
||||
- All accessibility improvements
|
||||
- Error handling
|
||||
- Browser history management
|
||||
- Keyboard shortcuts
|
||||
- Language preference storage
|
||||
- Loading states
|
||||
- Meta tags and SEO
|
||||
|
||||
2. **`/Users/txeo/Git/yo/cv/static/css/main-enhanced.css`**
|
||||
- Smooth transitions
|
||||
- Error toast styles
|
||||
- Enhanced focus states
|
||||
- Reduced motion support
|
||||
- High contrast mode support
|
||||
- Improved responsive design
|
||||
|
||||
**To apply these improvements:**
|
||||
```bash
|
||||
# Backup current files
|
||||
cp templates/index.html templates/index.html.backup
|
||||
cp static/css/main.css static/css/main.css.backup
|
||||
|
||||
# Apply improvements
|
||||
mv templates/index-improved.html templates/index.html
|
||||
mv static/css/main-enhanced.css static/css/main.css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Testing Commands
|
||||
|
||||
### Run the site
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
### Test HTMX endpoints
|
||||
```bash
|
||||
# Test initial load
|
||||
curl -s 'http://localhost:1999/?lang=en' | head -50
|
||||
|
||||
# Test HTMX partial
|
||||
curl -s 'http://localhost:1999/cv?lang=es' | head -50
|
||||
|
||||
# Test performance
|
||||
curl -o /dev/null -s -w "Time: %{time_total}s\n" 'http://localhost:1999/cv?lang=en'
|
||||
```
|
||||
|
||||
### Run Lighthouse audit
|
||||
```bash
|
||||
# Install if needed
|
||||
npm install -g lighthouse
|
||||
|
||||
# Run audit
|
||||
lighthouse http://localhost:1999/?lang=en --view
|
||||
```
|
||||
|
||||
### Test accessibility
|
||||
```bash
|
||||
# Install axe-cli
|
||||
npm install -g @axe-core/cli
|
||||
|
||||
# Run audit
|
||||
axe http://localhost:1999/?lang=en
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Summary
|
||||
|
||||
### Current Score: 85/100
|
||||
|
||||
**Breakdown:**
|
||||
- **Performance:** 100/100 ✅ (Exceptional sub-ms responses)
|
||||
- **HTMX Patterns:** 90/100 ✅ (Clean, well-structured)
|
||||
- **Accessibility:** 60/100 ⚠️ (Missing ARIA, keyboard nav)
|
||||
- **SEO:** 50/100 ⚠️ (Missing meta tags, structured data)
|
||||
- **Security:** 70/100 ⚠️ (Missing headers, SRI)
|
||||
- **Error Handling:** 40/100 ⚠️ (No user feedback)
|
||||
- **UX:** 80/100 ✅ (Good, needs transitions)
|
||||
|
||||
### Target Score: 100/100
|
||||
|
||||
**With recommended improvements:**
|
||||
- **Performance:** 100/100 ✅
|
||||
- **HTMX Patterns:** 100/100 ✅
|
||||
- **Accessibility:** 95/100 ✅
|
||||
- **SEO:** 95/100 ✅
|
||||
- **Security:** 95/100 ✅
|
||||
- **Error Handling:** 90/100 ✅
|
||||
- **UX:** 95/100 ✅
|
||||
|
||||
---
|
||||
|
||||
## 14. Questions?
|
||||
|
||||
If you need help implementing any of these recommendations:
|
||||
|
||||
1. **Accessibility:** Focus on WCAG 2.1 Level AA compliance
|
||||
2. **HTMX:** Follow htmx.org best practices
|
||||
3. **Go Backend:** Use standard library middleware patterns
|
||||
4. **Testing:** Prioritize automated accessibility and performance tests
|
||||
|
||||
**Resources:**
|
||||
- HTMX Docs: https://htmx.org/docs/
|
||||
- WCAG Guidelines: https://www.w3.org/WAI/WCAG21/quickref/
|
||||
- Go Security: https://go.dev/doc/security/
|
||||
- Lighthouse: https://developers.google.com/web/tools/lighthouse
|
||||
|
||||
---
|
||||
|
||||
**Next Steps:**
|
||||
1. Review this document
|
||||
2. Apply Phase 1 improvements (critical)
|
||||
3. Test with real users
|
||||
4. Iterate based on feedback
|
||||
5. Deploy to production
|
||||
|
||||
Your CV site has an excellent foundation. With these enhancements, it will be a best-in-class example of HTMX implementation! 🚀
|
||||
+22
-19
@@ -32,7 +32,7 @@ This website uses **Matomo**, a self-hosted, privacy-friendly analytics platform
|
||||
- `_pk_ref`: Attribution information (6 months)
|
||||
|
||||
**Data storage:**
|
||||
- All analytics data is stored on my own server (`matomo.drolo.club`)
|
||||
- All analytics data is stored on a self-hosted Matomo instance
|
||||
- Data is NOT shared with third parties
|
||||
- Data is NOT sold or used for advertising
|
||||
|
||||
@@ -66,27 +66,30 @@ If you have questions about this privacy policy or data handling:
|
||||
|
||||
## For Developers Using This Code
|
||||
|
||||
If you fork or use this code as a template:
|
||||
This privacy policy is a **template** showing how to handle self-hosted analytics. If you use this code:
|
||||
|
||||
1. **Update Matomo Site ID** in `templates/index.html`:
|
||||
```javascript
|
||||
_paq.push(['setSiteId', '4']); // Change to your Matomo site ID
|
||||
```
|
||||
### If You Want Analytics:
|
||||
1. Set up your own analytics service (Matomo, Plausible, etc.)
|
||||
2. Update the tracking code in `templates/index.html`:
|
||||
- Replace the analytics server URL with yours
|
||||
- Replace the site ID with yours
|
||||
3. Update CSP headers in `internal/middleware/security.go`:
|
||||
- Add your analytics domain to `script-src` and `connect-src`
|
||||
4. Rewrite this PRIVACY.md file with your own:
|
||||
- Contact information
|
||||
- Analytics provider details
|
||||
- Data handling practices
|
||||
|
||||
2. **Update Matomo Server URL**:
|
||||
```javascript
|
||||
var u="https://matomo.drolo.club/"; // Change to your Matomo instance
|
||||
```
|
||||
### If You Don't Want Analytics:
|
||||
1. Remove the analytics `<script>` block from `templates/index.html`
|
||||
2. Remove analytics domains from CSP headers in `internal/middleware/security.go`
|
||||
3. Simplify this PRIVACY.md to state "This site does not use analytics or tracking"
|
||||
|
||||
3. **Update this privacy policy** with your own contact information and data handling practices.
|
||||
|
||||
4. **Configure CSP headers** in `internal/middleware/security.go` to allow your Matomo domain:
|
||||
```go
|
||||
"script-src 'self' 'unsafe-inline' ... https://your-matomo-domain.com; "
|
||||
"connect-src 'self' ... https://your-matomo-domain.com; "
|
||||
```
|
||||
|
||||
5. **Remove Matomo entirely** if you don't want analytics - just delete the Matomo `<script>` block from `templates/index.html` and remove the matomo.drolo.club entries from CSP headers.
|
||||
### Template Variables to Replace:
|
||||
- `[YOUR-ANALYTICS-SERVER.COM]` - Your analytics server domain
|
||||
- `[YOUR-SITE-ID]` - Your analytics site identifier
|
||||
- `[YOUR-CONTACT-INFO]` - Your contact information
|
||||
- `[YOUR-GITHUB]` - Your GitHub profile/repository
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,440 +0,0 @@
|
||||
# Project Documentation - Final Summary
|
||||
|
||||
**Date:** November 9, 2025
|
||||
**Project:** CV/Resume Web Application (Personal Site)
|
||||
**Tech Stack:** Go 1.25.1 + HTMX 1.9.10
|
||||
**Status:** ✅ **Public GitHub Release Ready**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Project Intent
|
||||
|
||||
**This is a personal CV website** - NOT a template for public use.
|
||||
|
||||
While the code is open-source (MIT license), this project is designed for personal use and will be modified without notice. The documentation exists primarily for my own reference and to demonstrate professional development practices.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Documentation Overview
|
||||
|
||||
### What Was Created
|
||||
|
||||
**Total Files:** 14 documentation files
|
||||
**Total Documentation:** ~220 KB
|
||||
**Code Quality:** Production-ready
|
||||
**Documentation Quality:** Comprehensive
|
||||
|
||||
### Documentation Philosophy
|
||||
|
||||
All documentation includes clear disclaimers that this is a **personal project** that happens to be public, not a template intended for others to use.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Created Documentation Files
|
||||
|
||||
### Phase 1: Essential Legal/Community Files (4 files)
|
||||
|
||||
**1. LICENSE** (1.1 KB) - MIT License
|
||||
- Standard MIT License with 2025 copyright
|
||||
- Allows viewing and learning from the code
|
||||
- Clear legal framework
|
||||
|
||||
**2. CONTRIBUTING.md** (2.9 KB) - Template Usage Notice
|
||||
- **Clarifies this is NOT seeking contributions**
|
||||
- Explains this is a personal portfolio project
|
||||
- Provides guidance for those who want to fork
|
||||
- Directs security issues to SECURITY.md
|
||||
- Politely declines feature requests
|
||||
|
||||
**3. CODE_OF_CONDUCT.md** (5.3 KB) - Community Standards
|
||||
- Contributor Covenant 2.1 standard
|
||||
- Professional community expectations
|
||||
- Maintained for completeness
|
||||
|
||||
**4. SECURITY.md** (8.8 KB) - Security Policy
|
||||
- Vulnerability reporting process
|
||||
- Deployment security considerations
|
||||
- Privacy and security best practices
|
||||
|
||||
**Impact:** Legal safety + professional standards
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Enhanced Core Documentation (1 file)
|
||||
|
||||
**5. README.md** (Enhanced, ~9 KB)
|
||||
|
||||
**Added:**
|
||||
- **Project Status section** - Makes it crystal clear this is personal, not a template
|
||||
- Professional badges (Go, HTMX, License, Use Template)
|
||||
- Table of Contents
|
||||
- Documentation links section
|
||||
- Deployment options overview
|
||||
- Customization preview
|
||||
- Clear "Using This Template" section (explains it's MIT but personal)
|
||||
|
||||
**Key Changes:**
|
||||
- Removed "PRs Welcome" badge → Changed to "Use Template" badge
|
||||
- Added disclaimer: "personal CV project, feature-complete, not seeking contributions"
|
||||
- Updated all Docker references to non-Docker alternatives
|
||||
- Made it clear: free to fork, but not actively maintained for others
|
||||
|
||||
**Impact:** Sets clear expectations - this is a portfolio piece, not a community project
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Deployment Documentation (1 file)
|
||||
|
||||
**6. DEPLOYMENT.md** (45 KB, 1,054 lines) - **DOCKER-FREE**
|
||||
|
||||
**Deployment Methods Covered:**
|
||||
1. **VPS Deployment**
|
||||
- Systemd service setup
|
||||
- Nginx reverse proxy configuration
|
||||
- SSL/TLS with Let's Encrypt
|
||||
- Process management
|
||||
|
||||
2. **Cloud Platforms**
|
||||
- Fly.io (build from source)
|
||||
- Google Cloud Run (with minimal Dockerfile for Cloud Run only)
|
||||
- AWS EC2/Lightsail (VPS-style)
|
||||
- Railway/Render (auto-build)
|
||||
|
||||
3. **Manual Deployment**
|
||||
- Build from source
|
||||
- Standalone binary execution
|
||||
- Environment configuration
|
||||
|
||||
**What Was REMOVED:**
|
||||
- ❌ All Docker Compose references
|
||||
- ❌ Docker development environment
|
||||
- ❌ Docker Swarm orchestration
|
||||
- ❌ Kubernetes configurations
|
||||
- ❌ Container-first deployment mindset
|
||||
|
||||
**What Remains:**
|
||||
- ✅ VPS deployment (primary method)
|
||||
- ✅ Cloud platforms (where applicable without Docker)
|
||||
- ✅ Manual binary deployment
|
||||
- ✅ systemd service management
|
||||
- ✅ Nginx configuration
|
||||
- ✅ SSL/TLS setup
|
||||
|
||||
**Added Disclaimer:** "This is my personal CV website. This deployment guide is primarily for my own use."
|
||||
|
||||
**Impact:** Clean, focused deployment guide without Docker complexity
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Customization & API Documentation (2 files)
|
||||
|
||||
**7. CUSTOMIZATION.md** (38 KB, 1,674 lines)
|
||||
|
||||
**Content:**
|
||||
- Complete JSON schema documentation
|
||||
- Visual customization guide
|
||||
- Template modification examples
|
||||
- Advanced customization patterns
|
||||
- Testing workflows
|
||||
|
||||
**Added Disclaimer:** "This is my personal CV website... I don't intend for others to use this as a template - it's publicly available code, but it's designed for my personal use."
|
||||
|
||||
**8. API.md** (70 KB, 1,745 lines)
|
||||
|
||||
**Content:**
|
||||
- All 5 endpoints documented and tested
|
||||
- 192+ code examples
|
||||
- HTMX integration patterns
|
||||
- Performance metrics
|
||||
|
||||
**Added Disclaimer:** "This is my personal CV website API documentation... this API is designed for my personal site and may change without notice."
|
||||
|
||||
**Impact:** Complete documentation with honest intent
|
||||
|
||||
---
|
||||
|
||||
### Removed Files (Phase 5: Docker Cleanup)
|
||||
|
||||
**Docker Files DELETED (11 files, ~82 KB):**
|
||||
1. ❌ Dockerfile
|
||||
2. ❌ .dockerignore
|
||||
3. ❌ docker-compose.yml
|
||||
4. ❌ docker-compose.prod.yml
|
||||
5. ❌ docker-test.sh
|
||||
6. ❌ DOCKER.md
|
||||
7. ❌ DOCKER-QUICKSTART.md
|
||||
8. ❌ DOCKER-TESTING.md
|
||||
9. ❌ DOCKER-SUMMARY.md
|
||||
10. ❌ .docker-deployment-checklist.md
|
||||
11. ❌ DOCKER-README-ADDITION.md
|
||||
|
||||
**Reason:** User doesn't use Docker and doesn't want Docker-related content in the project.
|
||||
|
||||
---
|
||||
|
||||
## 📈 Project Readiness Assessment
|
||||
|
||||
### GitHub Public Release Checklist
|
||||
|
||||
**Essential (Required):**
|
||||
- ✅ LICENSE file (MIT)
|
||||
- ✅ README.md with clear project status
|
||||
- ✅ CONTRIBUTING.md (clarifies personal project)
|
||||
- ✅ CODE_OF_CONDUCT.md
|
||||
- ✅ SECURITY.md
|
||||
- ✅ .gitignore
|
||||
|
||||
**Documentation:**
|
||||
- ✅ DEPLOYMENT.md (VPS, cloud, manual - no Docker)
|
||||
- ✅ CUSTOMIZATION.md (with disclaimer)
|
||||
- ✅ API.md (with disclaimer)
|
||||
|
||||
**Clarity:**
|
||||
- ✅ All docs state "personal project"
|
||||
- ✅ Clear that it's not a template for others
|
||||
- ✅ Honest about intent (portfolio/showcase)
|
||||
- ✅ Professional but realistic
|
||||
|
||||
**Score:** 14/14 = **100% Ready for Public Release with Correct Expectations**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What This Project Communicates
|
||||
|
||||
### To Employers / Viewers
|
||||
|
||||
✅ **Professional Development Practices**
|
||||
- Clean architecture
|
||||
- Comprehensive documentation
|
||||
- Security-conscious
|
||||
- Production-ready code
|
||||
|
||||
✅ **Technical Skills**
|
||||
- Go programming
|
||||
- HTMX/Hypermedia patterns
|
||||
- Server deployment
|
||||
- Documentation writing
|
||||
|
||||
✅ **Honest Communication**
|
||||
- Clear about project purpose
|
||||
- Sets realistic expectations
|
||||
- Professional boundaries
|
||||
|
||||
### To Potential Contributors
|
||||
|
||||
✅ **Clear Boundaries**
|
||||
- "This is my personal CV"
|
||||
- "Not seeking contributions"
|
||||
- "Free to fork under MIT license"
|
||||
- "Security issues only, please"
|
||||
|
||||
✅ **Respectful Guidance**
|
||||
- Explains why contributions aren't accepted
|
||||
- Encourages forking if interested
|
||||
- Points to other open-source projects
|
||||
|
||||
---
|
||||
|
||||
## 📁 Final File Structure
|
||||
|
||||
```
|
||||
/Users/txeo/Git/yo/cv/
|
||||
├── LICENSE # MIT License
|
||||
├── README.md # Enhanced with disclaimers
|
||||
├── CONTRIBUTING.md # "Not seeking contributions"
|
||||
├── CODE_OF_CONDUCT.md # Community standards
|
||||
├── SECURITY.md # Security policy
|
||||
├── DEPLOYMENT.md # VPS/Cloud deployment (no Docker)
|
||||
├── CUSTOMIZATION.md # Personal customization docs
|
||||
├── API.md # API documentation
|
||||
├── API-QUICK-REFERENCE.md # Quick API reference
|
||||
└── PROJECT-DOCUMENTATION-SUMMARY.md # This file
|
||||
```
|
||||
|
||||
**Existing project documentation (unchanged):**
|
||||
```
|
||||
├── doc/
|
||||
│ ├── ARCHITECTURE.md # Technical architecture
|
||||
│ ├── SEO-OPTIMIZATION-COMPLETE.md
|
||||
│ └── HTMX-PRODUCTION-RECOMMENDATIONS.md
|
||||
```
|
||||
|
||||
**Total:** 14 documentation files (~220 KB)
|
||||
|
||||
---
|
||||
|
||||
## ✅ What Was Accomplished
|
||||
|
||||
### 1. Clear Project Intent ✅
|
||||
- Every major documentation file has a disclaimer
|
||||
- README makes it clear: personal project, not a template
|
||||
- CONTRIBUTING explains politely: not seeking contributions
|
||||
- Professional but honest
|
||||
|
||||
### 2. Docker Removal ✅
|
||||
- All 11 Docker files deleted
|
||||
- DEPLOYMENT.md rewritten without Docker
|
||||
- Focus on VPS, cloud platforms, manual deployment
|
||||
- Cleaner, simpler documentation
|
||||
|
||||
### 3. Documentation Quality ✅
|
||||
- Comprehensive deployment guide (VPS-focused)
|
||||
- Complete API documentation (tested)
|
||||
- Detailed customization guide
|
||||
- Professional standards maintained
|
||||
|
||||
### 4. Legal & Community ✅
|
||||
- MIT License (allows viewing/learning)
|
||||
- CODE_OF_CONDUCT (professional standards)
|
||||
- SECURITY.md (vulnerability reporting)
|
||||
- Clear contribution policy
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Lessons Demonstrated
|
||||
|
||||
This project showcases:
|
||||
|
||||
1. **Professional Development**
|
||||
- Production-grade code
|
||||
- Comprehensive documentation
|
||||
- Security best practices
|
||||
- Clean architecture
|
||||
|
||||
2. **Honest Communication**
|
||||
- Clear project boundaries
|
||||
- Realistic expectations
|
||||
- Professional but personal
|
||||
|
||||
3. **Open Source Understanding**
|
||||
- MIT license = code is viewable
|
||||
- Open ≠ seeking contributions
|
||||
- Personal projects can be public
|
||||
- Setting healthy boundaries
|
||||
|
||||
---
|
||||
|
||||
## 📊 Statistics
|
||||
|
||||
### Documentation
|
||||
- **Files:** 14 comprehensive docs
|
||||
- **Size:** ~220 KB
|
||||
- **Quality:** Professional, clear, honest
|
||||
|
||||
### Code
|
||||
- **Architecture:** Clean, production-ready
|
||||
- **Security:** Hardened (CSP, HSTS, etc.)
|
||||
- **Performance:** <10ms response times
|
||||
- **Testing:** API endpoints verified
|
||||
|
||||
### Deployment
|
||||
- **Methods:** 3 (VPS, Cloud, Manual)
|
||||
- **Docker:** Removed (11 files deleted)
|
||||
- **Focus:** Systemd + Nginx (primary)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Final Status
|
||||
|
||||
**GitHub Repository Status:** ✅ **READY FOR PUBLIC RELEASE**
|
||||
|
||||
**What viewers will see:**
|
||||
- Professional CV website with excellent code
|
||||
- Clear documentation stating "personal project"
|
||||
- MIT license allowing learning from the code
|
||||
- Polite boundaries around contributions
|
||||
- Portfolio-quality engineering
|
||||
|
||||
**What won't confuse people:**
|
||||
- No false expectations of being a "template"
|
||||
- No confusion about contribution acceptance
|
||||
- No Docker files if you don't use Docker
|
||||
- Clear, honest communication throughout
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Recommendations for Publishing
|
||||
|
||||
### Before Making Public
|
||||
|
||||
1. **Review GitHub Settings:**
|
||||
- Add description: "My personal CV website - Go + HTMX"
|
||||
- Add topics: `cv`, `resume`, `go`, `htmx`, `portfolio`, `personal-site`
|
||||
- Consider disabling Issues (Settings → Features → uncheck "Issues")
|
||||
- Consider disabling Discussions
|
||||
|
||||
2. **Repository Settings:**
|
||||
- Don't mark as "Template repository" (it's not a template)
|
||||
- Keep "Sponsorships" disabled (personal project)
|
||||
- Set visibility to Public
|
||||
|
||||
3. **Final Git Commands:**
|
||||
```bash
|
||||
# Review changes
|
||||
git status
|
||||
|
||||
# Add all documentation
|
||||
git add LICENSE CONTRIBUTING.md CODE_OF_CONDUCT.md SECURITY.md
|
||||
git add README.md DEPLOYMENT.md CUSTOMIZATION.md API.md
|
||||
git add PROJECT-DOCUMENTATION-SUMMARY.md
|
||||
|
||||
# Commit
|
||||
git commit -m "docs: finalize documentation as personal portfolio project
|
||||
|
||||
- Add clear disclaimers: personal site, not a template
|
||||
- Update CONTRIBUTING: not seeking contributions
|
||||
- Remove all Docker files and references (11 files)
|
||||
- Rewrite DEPLOYMENT.md without Docker (VPS/cloud focus)
|
||||
- Add project status sections to all major docs
|
||||
- Clarify MIT license but personal use
|
||||
|
||||
This is my personal CV site. While code is public (MIT),
|
||||
it's not intended as a template for others."
|
||||
|
||||
# Push
|
||||
git push origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💬 Suggested GitHub Description
|
||||
|
||||
**Repository Description:**
|
||||
```
|
||||
My personal CV/resume website built with Go and HTMX. Portfolio project showcasing production-grade development. Code is open-source (MIT) but designed for personal use, not as a template.
|
||||
```
|
||||
|
||||
**Topics to Add:**
|
||||
- `go`
|
||||
- `htmx`
|
||||
- `cv`
|
||||
- `resume`
|
||||
- `portfolio`
|
||||
- `personal-website`
|
||||
- `golang`
|
||||
- `chromedp`
|
||||
- `pdf-generation`
|
||||
|
||||
---
|
||||
|
||||
## ✨ Conclusion
|
||||
|
||||
Your CV website is now:
|
||||
|
||||
✅ **Professionally documented** - Comprehensive guides for all aspects
|
||||
✅ **Legally clear** - MIT license with appropriate disclaimers
|
||||
✅ **Honestly presented** - Personal project, not a public template
|
||||
✅ **Docker-free** - Removed all Docker content as requested
|
||||
✅ **Public-ready** - Can be made public without confusion
|
||||
|
||||
**The project demonstrates excellent engineering practices while maintaining clear, honest boundaries about its purpose as a personal portfolio piece.**
|
||||
|
||||
---
|
||||
|
||||
**Ready to publish?** The repository clearly communicates:
|
||||
- High-quality code worth viewing
|
||||
- Personal project with professional standards
|
||||
- Open-source for learning, not for template use
|
||||
- Honest, respectful communication
|
||||
|
||||
**Go ahead and make it public!** 🚀
|
||||
@@ -1,360 +0,0 @@
|
||||
# CV Site - Project Status
|
||||
|
||||
**Project**: Go + HTMX CV Website
|
||||
**Last Updated**: November 2025
|
||||
**Current Status**: Production-Ready Core, Roadmap for Future Enhancements
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This CV website is **production-ready** with a solid foundation:
|
||||
- ✅ Fast JSON caching (4.5x performance improvement)
|
||||
- ✅ Critical security vulnerabilities fixed (command injection, XSS)
|
||||
- ✅ International SEO (hreflang tags)
|
||||
- ✅ Clean, maintainable codebase
|
||||
- ✅ Bilingual support (EN/ES)
|
||||
- ✅ PDF export functionality
|
||||
- ✅ HTMX-powered dynamic updates
|
||||
|
||||
The application is secure, fast, and reliable for production deployment.
|
||||
|
||||
---
|
||||
|
||||
## What's Actually Implemented
|
||||
|
||||
### ✅ Phase 1: Core Improvements (COMPLETE)
|
||||
|
||||
#### 1.1 JSON Caching ✅
|
||||
**Status**: Fully implemented and tested
|
||||
**Files**: `internal/cache/cv_cache.go` (189 lines)
|
||||
|
||||
**Results**:
|
||||
- Response time: 10ms → 2.2ms (**4.5x faster**)
|
||||
- Throughput: 200/s → 1,308/s (**6.5x increase**)
|
||||
- Cache hit rate: 99%
|
||||
- Memory usage: <1MB
|
||||
|
||||
**Implementation**:
|
||||
```go
|
||||
// Production-grade caching with sync.RWMutex
|
||||
type Cache struct {
|
||||
data sync.Map
|
||||
mu sync.RWMutex
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 Command Injection Fix ✅
|
||||
**Status**: Fully implemented and validated
|
||||
**File**: `internal/handlers/cv.go:validateRepoPath()`
|
||||
|
||||
**Security**:
|
||||
- CWE-78 vulnerability eliminated
|
||||
- CVSS 9.8 → 0.0
|
||||
- Path validation with project directory whitelist
|
||||
|
||||
**Implementation**:
|
||||
```go
|
||||
func validateRepoPath(path string) error {
|
||||
absPath, _ := filepath.Abs(path)
|
||||
projectRoot, _ := filepath.Abs(".")
|
||||
if !strings.HasPrefix(absPath, projectRoot) {
|
||||
return fmt.Errorf("repository path outside project directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**Attack vectors blocked**:
|
||||
- ❌ Path traversal: `../../etc/passwd`
|
||||
- ❌ Absolute paths: `/etc/passwd`
|
||||
- ❌ Command injection: `data; rm -rf /`
|
||||
- ❌ Pipe injection: `data | cat /etc/passwd`
|
||||
|
||||
#### 1.3 XSS Protection ✅
|
||||
**Status**: Partially implemented (automatic HTML escaping enabled)
|
||||
**Files**: `internal/templates/template.go`, `templates/cv-content.html`
|
||||
|
||||
**Security**:
|
||||
- Removed unsafe `safeHTML` template function
|
||||
- Go's automatic HTML escaping active
|
||||
- CWE-79 risk reduced
|
||||
|
||||
**Note**: Full XSS prevention requires content review to ensure no unescaped HTML remains.
|
||||
|
||||
#### 1.4 International SEO - Hreflang Tags ✅
|
||||
**Status**: Fully implemented
|
||||
**Files**: `templates/index.html`, `internal/handlers/cv.go`
|
||||
|
||||
**Implementation**:
|
||||
```html
|
||||
<link rel="canonical" href="https://juan.andres.morenorub.io/?lang={{.Lang}}">
|
||||
<link rel="alternate" hreflang="en" href="https://juan.andres.morenorub.io/?lang=en">
|
||||
<link rel="alternate" hreflang="es" href="https://juan.andres.morenorub.io/?lang=es">
|
||||
<link rel="alternate" hreflang="x-default" href="https://juan.andres.morenorub.io/?lang=en">
|
||||
```
|
||||
|
||||
**SEO Impact**:
|
||||
- ✅ No duplicate content penalty
|
||||
- ✅ Correct language targeting
|
||||
- ✅ Better international search rankings
|
||||
|
||||
### ✅ Core Features (COMPLETE)
|
||||
|
||||
- **Bilingual Support**: Spanish and English with instant HTMX switching
|
||||
- **PDF Export**: Server-side generation using chromedp
|
||||
- **Browser Print**: Print-friendly layouts
|
||||
- **HTMX Interactivity**: Dynamic updates without page reload
|
||||
- **JSON-Based Content**: Easy updates via `data/cv-en.json` and `data/cv-es.json`
|
||||
- **Responsive Design**: Mobile, tablet, desktop optimized
|
||||
- **Security Headers**: CSP, X-Frame-Options, HSTS (production)
|
||||
- **Rate Limiting**: 3 PDF generations per minute per IP
|
||||
- **Origin Checking**: Prevents external hotlinking of PDF endpoint
|
||||
- **Graceful Shutdown**: Proper HTTP server lifecycle management
|
||||
|
||||
### Phase 1 Metrics
|
||||
|
||||
| Metric | Before | After | Improvement |
|
||||
|--------|--------|-------|-------------|
|
||||
| Response Time | ~10ms | 2.2ms | 4.5x faster |
|
||||
| Throughput | ~200/s | 1,308/s | 6.5x increase |
|
||||
| Critical Vulnerabilities | 2 | 0 | Eliminated |
|
||||
| SEO Score | 5/10 | 9/10 | +80% |
|
||||
|
||||
---
|
||||
|
||||
## What's Planned (Not Yet Implemented)
|
||||
|
||||
### 🔲 Phase 2: Advanced Security Hardening (PLANNED)
|
||||
|
||||
These features are documented but **not yet implemented**:
|
||||
|
||||
#### 2.1 CSP Nonce Implementation ❌
|
||||
**Status**: Not implemented
|
||||
**Planned file**: `internal/middleware/csp.go` (doesn't exist)
|
||||
|
||||
**Current state**: CSP uses `unsafe-inline` for scripts
|
||||
**Goal**: Remove `unsafe-inline`, implement nonce-based CSP
|
||||
|
||||
#### 2.2 Rate Limiter IP Validation Enhancement ❌
|
||||
**Status**: Partially implemented
|
||||
**Missing**: `RateLimiterConfig` struct, `Shutdown()` method
|
||||
|
||||
**Current implementation**: Basic rate limiting exists
|
||||
**Planned enhancement**: Trusted proxy support, proper IP validation
|
||||
|
||||
#### 2.3 Goroutine Leak Fix ❌
|
||||
**Status**: Not needed (current implementation doesn't leak)
|
||||
**Planned file**: `RateLimiter.Shutdown()` method
|
||||
|
||||
**Current state**: Rate limiter cleanup goroutine runs for application lifetime
|
||||
**Note**: Not a critical issue for production deployment
|
||||
|
||||
#### 2.4 Comprehensive Input Validation ❌
|
||||
**Status**: Not implemented
|
||||
**Planned file**: `internal/validator/validator.go` (doesn't exist)
|
||||
|
||||
**Current state**: Basic validation in handlers
|
||||
**Goal**: Centralized validation library with:
|
||||
- Whitelist-based validation
|
||||
- Suspicious pattern detection
|
||||
- Path traversal prevention
|
||||
- Defense-in-depth validation middleware
|
||||
|
||||
### 🔲 Phase 3: Testing Foundation (MINIMAL)
|
||||
|
||||
**Current Status**: 1 test file (`internal/handlers/cv_security_test.go`)
|
||||
|
||||
**Planned** (but not yet implemented):
|
||||
- [ ] Test infrastructure setup (`internal/testutil/`)
|
||||
- [ ] Unit tests for handlers
|
||||
- [ ] Security validation tests
|
||||
- [ ] HTMX interaction tests
|
||||
- [ ] Coverage target: 70%+
|
||||
|
||||
**Current Test Coverage**: Minimal (1 test file only)
|
||||
|
||||
---
|
||||
|
||||
## Production Readiness Assessment
|
||||
|
||||
### ✅ Safe to Deploy
|
||||
|
||||
The application is **production-ready** with:
|
||||
- ✅ Critical vulnerabilities fixed
|
||||
- ✅ Performance optimized (4.5x improvement)
|
||||
- ✅ Clean codebase
|
||||
- ✅ Proper error handling
|
||||
- ✅ Graceful shutdown
|
||||
- ✅ Security headers configured
|
||||
- ✅ Rate limiting active
|
||||
- ✅ Origin checking on sensitive endpoints
|
||||
|
||||
### 🟡 Recommended Improvements
|
||||
|
||||
Before deployment, consider:
|
||||
- 🟡 Increase test coverage (currently minimal)
|
||||
- 🟡 Implement CSP nonce for stronger XSS protection
|
||||
- 🟡 Add comprehensive input validation library
|
||||
- 🟡 Monitor rate limiter effectiveness in production
|
||||
|
||||
### Risk Assessment
|
||||
|
||||
**Low Risk** (safe to deploy):
|
||||
- ✅ Caching implementation (graceful fallback on errors)
|
||||
- ✅ Command injection fix (comprehensive path validation)
|
||||
- ✅ XSS protection (automatic HTML escaping)
|
||||
- ✅ International SEO (hreflang tags)
|
||||
|
||||
**Medium Risk** (monitor in production):
|
||||
- 🟡 Test coverage (minimal tests, but core functionality stable)
|
||||
- 🟡 CSP headers (allows `unsafe-inline` for now)
|
||||
- 🟡 Rate limiting (basic implementation, works but could be enhanced)
|
||||
|
||||
---
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
### Pre-Deployment
|
||||
|
||||
```bash
|
||||
# 1. Build production binary
|
||||
make build
|
||||
|
||||
# 2. Test the build
|
||||
GO_ENV=production PORT=1999 ./cv-server
|
||||
|
||||
# 3. Verify endpoints
|
||||
curl -I http://localhost:1999/
|
||||
curl -I http://localhost:1999/health
|
||||
curl -I http://localhost:1999/?lang=es
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
**Required variables** (`.env` file):
|
||||
```bash
|
||||
GO_ENV=production
|
||||
PORT=1999
|
||||
ALLOWED_ORIGINS=your-domain.com
|
||||
```
|
||||
|
||||
**Optional variables**:
|
||||
```bash
|
||||
CACHE_TTL_MINUTES=60
|
||||
TEMPLATE_HOT_RELOAD=false
|
||||
```
|
||||
|
||||
### Post-Deployment
|
||||
|
||||
- ✅ Verify language switching (EN ↔ ES)
|
||||
- ✅ Test PDF export functionality
|
||||
- ✅ Check security headers in browser DevTools
|
||||
- ✅ Monitor `/health` endpoint
|
||||
- ✅ Verify cache hit rate in logs
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements Roadmap
|
||||
|
||||
### Priority 1: Testing
|
||||
- [ ] Implement test infrastructure (`internal/testutil/`)
|
||||
- [ ] Add unit tests for core functions
|
||||
- [ ] Achieve 70%+ test coverage
|
||||
- [ ] Add E2E tests with Playwright
|
||||
|
||||
### Priority 2: Security
|
||||
- [ ] Implement CSP nonce system
|
||||
- [ ] Create centralized input validation library
|
||||
- [ ] Add comprehensive security test suite
|
||||
- [ ] Implement rate limiter shutdown for graceful cleanup
|
||||
|
||||
### Priority 3: Features
|
||||
- [ ] Complete PDF export optimization
|
||||
- [ ] Add contact form with validation
|
||||
- [ ] Implement analytics dashboard
|
||||
- [ ] Add downloadable resume in multiple formats
|
||||
|
||||
### Priority 4: Observability
|
||||
- [ ] Implement structured logging (slog)
|
||||
- [ ] Add Prometheus metrics endpoint
|
||||
- [ ] Set up Grafana dashboards
|
||||
- [ ] Implement distributed tracing
|
||||
|
||||
### Priority 5: Code Quality
|
||||
- [ ] Refactor `calculateDuration()` (reduce complexity)
|
||||
- [ ] Extract handler duplication
|
||||
- [ ] Split large CSS file into modules
|
||||
- [ ] Implement CSS variables for theming
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
### Implemented
|
||||
- `internal/cache/cv_cache.go` - JSON caching implementation
|
||||
- `internal/handlers/cv.go` - Core handlers with security fixes
|
||||
- `internal/middleware/security.go` - Security headers, rate limiting, origin checking
|
||||
- `internal/models/cv.go` - Data models with cache integration
|
||||
- `templates/index.html` - Main template with hreflang tags
|
||||
- `main.go` - Application entry point
|
||||
|
||||
### Planned (Not Yet Created)
|
||||
- `internal/validator/validator.go` - Input validation library
|
||||
- `internal/middleware/csp.go` - CSP nonce generation
|
||||
- `internal/middleware/validation.go` - Validation middleware
|
||||
- `internal/testutil/testutil.go` - Test utilities
|
||||
- Multiple test files (`*_test.go`)
|
||||
|
||||
---
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
make dev # Run with hot reload
|
||||
|
||||
# Build
|
||||
make build # Build production binary
|
||||
|
||||
# Testing (minimal tests currently)
|
||||
go test ./... # Run existing tests
|
||||
go test -v -run Security # Run security tests
|
||||
|
||||
# Deployment
|
||||
make install-service # Install as systemd service
|
||||
make update-service # Update running service
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[README.md](../README.md)** - Project overview and quick start
|
||||
- **[DEPLOYMENT.md](DEPLOYMENT.md)** - Production deployment guides
|
||||
- **[CUSTOMIZATION.md](CUSTOMIZATION.md)** - Template customization guide
|
||||
- **[API.md](API.md)** - HTTP endpoints documentation
|
||||
- **[SECURITY.md](SECURITY.md)** - Security policy and best practices
|
||||
- **[PRIVACY.md](PRIVACY.md)** - Privacy policy and analytics
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This CV website is **production-ready** with a solid foundation:
|
||||
- Core functionality complete and tested in production
|
||||
- Critical security vulnerabilities eliminated
|
||||
- Performance optimized with caching
|
||||
- Clean, maintainable codebase
|
||||
|
||||
While additional features like comprehensive testing, CSP nonces, and input validation libraries are planned for future enhancement, the current implementation is secure and performant enough for production deployment.
|
||||
|
||||
**Status**: ✅ **PRODUCTION READY**
|
||||
|
||||
**Recommended Action**: Deploy with confidence, implement Phase 2/3 enhancements as time allows.
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Created**: November 2025
|
||||
**Next Review**: Before implementing Phase 2 features
|
||||
+291
-4
@@ -98,12 +98,29 @@ Sensitive configuration is managed via environment variables:
|
||||
- Configure TLS certificates (Let's Encrypt recommended)
|
||||
- Consider using a reverse proxy (nginx, Caddy) for TLS termination
|
||||
|
||||
### 5. Rate Limiting
|
||||
### 5. Rate Limiting & Origin Checking
|
||||
|
||||
The application does not include built-in rate limiting:
|
||||
**Status:** ✅ **Implemented**
|
||||
|
||||
- **Recommendation**: Use a reverse proxy (nginx, Caddy) to implement rate limiting
|
||||
- Protect the `/export/pdf` endpoint to prevent PDF generation abuse
|
||||
The application includes built-in rate limiting and origin checking for resource-intensive endpoints:
|
||||
|
||||
**Protected Endpoints:**
|
||||
- `/export/pdf` - Rate limited to 3 requests per minute per IP
|
||||
- Origin checking prevents external hotlinking
|
||||
|
||||
**Configuration:**
|
||||
```bash
|
||||
# Set allowed domains in production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- IP-based rate limiting (3 PDF/min per IP)
|
||||
- Origin/Referer header validation
|
||||
- Works with reverse proxies (Nginx, CloudFlare)
|
||||
- Automatic IP detection from X-Forwarded-For headers
|
||||
|
||||
For detailed configuration, see [API Protection Features](#api-protection-features) below.
|
||||
|
||||
### 6. Input Validation
|
||||
|
||||
@@ -152,6 +169,276 @@ If deploying via Docker:
|
||||
- Scan images for vulnerabilities (e.g., `docker scan`, Trivy)
|
||||
- Keep base images updated
|
||||
|
||||
## API Protection Features
|
||||
|
||||
The application implements multiple layers of protection to prevent external access and DDoS attacks on resource-intensive endpoints.
|
||||
|
||||
### Origin Checking
|
||||
|
||||
**Purpose:** Prevent external sites from hotlinking to resource-intensive endpoints like PDF generation.
|
||||
|
||||
**How It Works:**
|
||||
1. Checks `Origin` header (CORS requests)
|
||||
2. Falls back to `Referer` header (navigation requests)
|
||||
3. Validates against whitelist of allowed domains
|
||||
4. Blocks requests from external domains
|
||||
|
||||
**Configuration via Environment Variable:**
|
||||
|
||||
```bash
|
||||
# Development (default - allows localhost)
|
||||
ALLOWED_ORIGINS=
|
||||
|
||||
# Production (specify your domains)
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
|
||||
# Multiple domains
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com,staging.yourdomain.com
|
||||
```
|
||||
|
||||
**Behavior by Environment:**
|
||||
|
||||
| Environment | No Header | Localhost | Your Domain | External Domain |
|
||||
|-------------|-----------|-----------|-------------|-----------------|
|
||||
| **Development** | ✅ Allowed | ✅ Allowed | ✅ Allowed | ❌ Blocked |
|
||||
| **Production** | ❌ Blocked (PDF) | ✅ Allowed | ✅ Allowed | ❌ Blocked |
|
||||
|
||||
**Example Requests:**
|
||||
|
||||
```bash
|
||||
# ✅ Allowed (localhost in development)
|
||||
curl http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ✅ Allowed (valid referer)
|
||||
curl -H "Referer: https://yourdomain.com/" \
|
||||
https://yourdomain.com/export/pdf?lang=en
|
||||
|
||||
# ❌ Blocked (external referer)
|
||||
curl -H "Referer: https://evil.com/" \
|
||||
https://yourdomain.com/export/pdf?lang=en
|
||||
# Response: 403 Forbidden - External access not allowed
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
**Purpose:** Prevent abuse even from allowed origins by limiting request frequency.
|
||||
|
||||
**Current Configuration:**
|
||||
- **Endpoint:** `/export/pdf`
|
||||
- **Limit:** 3 requests per minute per IP address
|
||||
- **Window:** 1 minute (rolling)
|
||||
- **Response:** 429 Too Many Requests when exceeded
|
||||
|
||||
**Implementation:**
|
||||
```go
|
||||
// Applied in main.go
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
| Requests | Status | Response |
|
||||
|----------|--------|----------|
|
||||
| 1st-3rd requests | ✅ 200 OK | PDF generated |
|
||||
| 4th+ request (within 1 min) | ❌ 429 Too Many Requests | Rate limit exceeded |
|
||||
| After 1 minute | ✅ 200 OK | Counter reset |
|
||||
|
||||
**IP Detection:**
|
||||
- Checks `X-Forwarded-For` header (proxy/CDN scenarios)
|
||||
- Falls back to `X-Real-IP` header (alternative proxy header)
|
||||
- Uses `RemoteAddr` field (direct connections)
|
||||
- Works correctly behind Nginx, CloudFlare, and other reverse proxies
|
||||
|
||||
**Rate Limit Response:**
|
||||
```http
|
||||
HTTP/1.1 429 Too Many Requests
|
||||
Retry-After: 60
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
```
|
||||
|
||||
### Combined Protection
|
||||
|
||||
The PDF endpoint has **both** origin checking and rate limiting applied in sequence:
|
||||
|
||||
```
|
||||
Request Flow:
|
||||
User Request → Origin Checker → Rate Limiter → PDF Handler
|
||||
↓ ↓
|
||||
403 if external 429 if exceeded
|
||||
```
|
||||
|
||||
**Protection Scenarios:**
|
||||
|
||||
1. **Scenario: External hotlinking attempt**
|
||||
- External site links directly to your PDF endpoint
|
||||
- **Mitigation:** Origin checker blocks (403 Forbidden)
|
||||
- **Result:** Request never reaches rate limiter
|
||||
|
||||
2. **Scenario: Rapid PDF generation from your site**
|
||||
- Legitimate user on your domain requests many PDFs quickly
|
||||
- **Mitigation:** Rate limiter blocks after 3 requests/min (429)
|
||||
- **Result:** Prevents resource exhaustion
|
||||
|
||||
3. **Scenario: Distributed attack with header spoofing**
|
||||
- Botnet spoofs Referer headers to bypass origin check
|
||||
- **Mitigation:** HTTPS prevents header modification + Rate limiter per IP
|
||||
- **Result:** Each IP limited to 3 requests/min
|
||||
|
||||
### Configuration Examples
|
||||
|
||||
**Development Environment:**
|
||||
```bash
|
||||
# .env file
|
||||
PORT=1999
|
||||
HOST=localhost
|
||||
GO_ENV=development
|
||||
ALLOWED_ORIGINS= # Empty allows localhost
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Allows localhost and 127.0.0.1
|
||||
- Allows requests without headers
|
||||
- Rate limit: 3 PDF/min per IP
|
||||
|
||||
**Production Environment:**
|
||||
```bash
|
||||
# .env file
|
||||
PORT=1999
|
||||
HOST=0.0.0.0
|
||||
GO_ENV=production
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- Only allows specified domains
|
||||
- Requires Origin or Referer header for PDF endpoint
|
||||
- Blocks direct URL access
|
||||
- Rate limit: 3 PDF/min per IP
|
||||
|
||||
### Testing Protection
|
||||
|
||||
**Test Origin Checking:**
|
||||
```bash
|
||||
# ✅ Should succeed (localhost in development)
|
||||
curl http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ✅ Should succeed (valid referer)
|
||||
curl -H "Referer: http://localhost:1999/" \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
|
||||
# ❌ Should fail (external referer)
|
||||
curl -H "Referer: https://evil.com/" \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
# Expected: 403 Forbidden
|
||||
```
|
||||
|
||||
**Test Rate Limiting:**
|
||||
```bash
|
||||
# Generate 4 PDFs quickly to trigger rate limit
|
||||
for i in {1..4}; do
|
||||
echo "Request $i:"
|
||||
curl -w "Status: %{http_code}\n" -o /dev/null -s \
|
||||
http://localhost:1999/export/pdf?lang=en
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Expected output:
|
||||
# Request 1: Status: 200
|
||||
# Request 2: Status: 200
|
||||
# Request 3: Status: 200
|
||||
# Request 4: Status: 429
|
||||
```
|
||||
|
||||
### Customizing Protection
|
||||
|
||||
**Adjust Rate Limits:**
|
||||
|
||||
Edit `main.go` to change limits:
|
||||
|
||||
```go
|
||||
// Current: 3 requests per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(3, 1*time.Minute)
|
||||
|
||||
// More restrictive: 5 per hour
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Hour)
|
||||
|
||||
// Less restrictive: 10 per minute
|
||||
pdfRateLimiter := middleware.NewRateLimiter(10, 1*time.Minute)
|
||||
```
|
||||
|
||||
**Apply Protection to Other Endpoints:**
|
||||
|
||||
```go
|
||||
// Protect /cv endpoint
|
||||
protectedCVHandler := middleware.OriginChecker(
|
||||
http.HandlerFunc(cvHandler.CVContent),
|
||||
)
|
||||
mux.Handle("/cv", protectedCVHandler)
|
||||
```
|
||||
|
||||
### Production Deployment Checklist
|
||||
|
||||
Before deploying to production:
|
||||
|
||||
- [ ] Set `GO_ENV=production` in environment
|
||||
- [ ] Configure `ALLOWED_ORIGINS` with your domain(s)
|
||||
- [ ] Test origin checking with external domain
|
||||
- [ ] Test rate limiting with rapid requests
|
||||
- [ ] Verify HTTPS is enabled (prevents header spoofing)
|
||||
- [ ] Set up monitoring for 403/429 responses
|
||||
- [ ] Configure log retention for security analysis
|
||||
- [ ] Test PDF generation under load
|
||||
- [ ] Verify reverse proxy headers (X-Forwarded-For)
|
||||
- [ ] Document allowed origins in runbook
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Problem: Legitimate users getting 403 Forbidden**
|
||||
|
||||
**Cause:** `ALLOWED_ORIGINS` not configured correctly
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Ensure all your domains are listed (comma-separated, no spaces)
|
||||
ALLOWED_ORIGINS=yourdomain.com,www.yourdomain.com
|
||||
|
||||
# Check for typos (domain matching is case-insensitive but exact)
|
||||
# Verify in logs which domain is being rejected
|
||||
```
|
||||
|
||||
**Problem: Rate limit too restrictive**
|
||||
|
||||
**Cause:** Legitimate users hitting the 3 requests/min limit
|
||||
|
||||
**Solution:**
|
||||
```go
|
||||
// Increase limit in main.go
|
||||
pdfRateLimiter := middleware.NewRateLimiter(5, 1*time.Minute) // or higher
|
||||
```
|
||||
|
||||
**Problem: Behind reverse proxy, rate limiting not working per IP**
|
||||
|
||||
**Cause:** IP detection failing, all requests seen as same IP
|
||||
|
||||
**Solution:**
|
||||
```nginx
|
||||
# Ensure Nginx passes correct headers
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
```
|
||||
|
||||
**Problem: Origin header not being sent**
|
||||
|
||||
**Cause:** Browser doesn't send Origin for same-origin navigation requests
|
||||
|
||||
**Solution:** This is normal browser behavior. The middleware checks `Referer` header as fallback, which browsers do send for navigation.
|
||||
|
||||
For technical API details, see [API.md](API.md#security-protection).
|
||||
|
||||
---
|
||||
|
||||
## Known Security Considerations
|
||||
|
||||
### PDF Generation Resource Usage
|
||||
|
||||
|
||||
@@ -1,693 +0,0 @@
|
||||
# SEO Optimization Complete ✅
|
||||
|
||||
**Date:** October 30, 2025
|
||||
**Time Required:** 1.5 hours
|
||||
**Status:** Fully implemented and tested
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
Comprehensive SEO optimization with meta tags, Open Graph, social media cards, JSON-LD structured data, sitemap, and robots.txt for maximum search engine visibility and social media sharing.
|
||||
|
||||
---
|
||||
|
||||
## ✅ What Was Implemented
|
||||
|
||||
### 1. **Primary Meta Tags** (Enhanced)
|
||||
|
||||
**Location:** `templates/index.html` (`<head>` section)
|
||||
|
||||
```html
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Juan Andrés Moreno Rubio - Curriculum Vitae</title>
|
||||
<meta name="title" content="Juan Andrés Moreno Rubio - Professional CV">
|
||||
<meta name="description" content="Lead Technical Consultant, FullStack Developer | 18 years of experience in web development, SAP CDC, React, Node.js, Go, HTMX and AI-assisted development">
|
||||
<meta name="keywords" content="CV, Resume, Juan Andrés Moreno Rubio, FullStack Developer, SAP CDC, React, Node.js, Go, HTMX, AI, Web Development, Technical Consultant">
|
||||
<meta name="author" content="Juan Andrés Moreno Rubio">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="https://juan.andres.morenoyrubio.com">
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Bilingual Descriptions** (English/Spanish auto-switching)
|
||||
- ✅ **Rich Keywords** (18+ technology keywords)
|
||||
- ✅ **Author Attribution**
|
||||
- ✅ **Search Engine Instructions** (index, follow)
|
||||
- ✅ **Canonical URL** (prevents duplicate content)
|
||||
|
||||
**SEO Impact:**
|
||||
- Better search result snippets
|
||||
- Improved keyword ranking
|
||||
- Proper attribution
|
||||
- Duplicate content prevention
|
||||
|
||||
---
|
||||
|
||||
### 2. **Open Graph Meta Tags** (Social Media)
|
||||
|
||||
**Location:** `templates/index.html` (`<head>` section)
|
||||
|
||||
```html
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="profile">
|
||||
<meta property="og:url" content="https://juan.andres.morenoyrubio.com">
|
||||
<meta property="og:title" content="Juan Andrés Moreno Rubio - Professional CV">
|
||||
<meta property="og:description" content="Senior Technical Consultant with 18 years of experience">
|
||||
<meta property="og:image" content="https://juan.andres.morenoyrubio.com/static/images/profile.jpg">
|
||||
<meta property="og:locale" content="en_US"> <!-- or es_ES for Spanish -->
|
||||
<meta property="og:site_name" content="Juan Andrés Moreno Rubio">
|
||||
<meta property="profile:first_name" content="Juan Andrés">
|
||||
<meta property="profile:last_name" content="Moreno Rubio">
|
||||
<meta property="profile:username" content="txeo">
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Profile Type** (optimized for personal CV)
|
||||
- ✅ **Dynamic Locale** (en_US / es_ES based on language)
|
||||
- ✅ **Profile Metadata** (first name, last name, username)
|
||||
- ✅ **Image Support** (profile photo for rich previews)
|
||||
- ✅ **Bilingual Descriptions** (auto-switching)
|
||||
|
||||
**Social Media Impact:**
|
||||
- Rich preview cards on Facebook
|
||||
- Rich preview cards on LinkedIn
|
||||
- Professional appearance when shared
|
||||
- Increased click-through rates
|
||||
|
||||
---
|
||||
|
||||
### 3. **Twitter/X Card Meta Tags**
|
||||
|
||||
**Location:** `templates/index.html` (`<head>` section)
|
||||
|
||||
```html
|
||||
<!-- Social Media Card (Generic) -->
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Juan Andrés Moreno Rubio - Professional CV">
|
||||
<meta name="twitter:description" content="Lead Technical Consultant, FullStack Developer">
|
||||
<meta name="twitter:image" content="https://juan.andres.morenoyrubio.com/static/images/profile.jpg">
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Summary Card** (compact, professional)
|
||||
- ✅ **No Twitter Handle** (per your preference)
|
||||
- ✅ **Generic Social Sharing** (works on any platform)
|
||||
- ✅ **Image Support** (profile photo)
|
||||
|
||||
**Social Media Impact:**
|
||||
- Works on X/Twitter (if shared)
|
||||
- Works on other platforms (generic meta tags)
|
||||
- Professional preview cards
|
||||
|
||||
---
|
||||
|
||||
### 4. **JSON-LD Structured Data** (Schema.org)
|
||||
|
||||
**Location:** `templates/index.html` (`<head>` section, before `</head>`)
|
||||
|
||||
```html
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Juan Andrés Moreno Rubio",
|
||||
"jobTitle": "Lead Technical Consultant, FullStack Developer",
|
||||
"url": "https://juan.andres.morenoyrubio.com",
|
||||
"email": "txeo.msx@gmail.com",
|
||||
"telephone": "+34 676875420",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": "Arrecife, Las Palmas de Gran Canaria, Spain"
|
||||
},
|
||||
"sameAs": [
|
||||
"https://www.linkedin.com/in/juan-andres-moreno-rubio",
|
||||
"https://github.com/juanatsap",
|
||||
"https://www.behance.net/txeo"
|
||||
],
|
||||
"alumniOf": {
|
||||
"@type": "EducationalOrganization",
|
||||
"name": "Universidad de Extremadura"
|
||||
},
|
||||
"knowsAbout": [
|
||||
"Web Development",
|
||||
"SAP Customer Data Cloud",
|
||||
"React",
|
||||
"Node.js",
|
||||
"Go",
|
||||
"HTMX",
|
||||
"AI-Assisted Development",
|
||||
"Full Stack Development"
|
||||
],
|
||||
"worksFor": {
|
||||
"@type": "Organization",
|
||||
"name": "Olympic Broadcasting Services"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Person Schema** (Google understands this is a person)
|
||||
- ✅ **Contact Information** (email, phone, location)
|
||||
- ✅ **Social Profiles** (LinkedIn, GitHub, Behance)
|
||||
- ✅ **Education** (Universidad de Extremadura)
|
||||
- ✅ **Skills/Knowledge** (8 key technologies)
|
||||
- ✅ **Employment** (current employer)
|
||||
|
||||
**SEO Impact:**
|
||||
- **Google Knowledge Graph** eligibility
|
||||
- **Rich Search Results** (contact info, social links)
|
||||
- **Professional Profile** in search results
|
||||
- **Better job search visibility**
|
||||
- **Structured data validation** passes
|
||||
|
||||
---
|
||||
|
||||
### 5. **Sitemap.xml** (Search Engine Discovery)
|
||||
|
||||
**Location:** `static/sitemap.xml`
|
||||
**URL:** `https://juan.andres.morenoyrubio.com/static/sitemap.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<!-- English Version -->
|
||||
<url>
|
||||
<loc>https://juan.andres.morenoyrubio.com/?lang=en</loc>
|
||||
<lastmod>2024-10-18</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
<xhtml:link rel="alternate" hreflang="es" href=".../?lang=es"/>
|
||||
<xhtml:link rel="alternate" hreflang="en" href=".../?lang=en"/>
|
||||
</url>
|
||||
|
||||
<!-- Spanish Version -->
|
||||
<url>
|
||||
<loc>https://juan.andres.morenoyrubio.com/?lang=es</loc>
|
||||
<lastmod>2024-10-18</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
<xhtml:link rel="alternate" hreflang="es" href=".../?lang=es"/>
|
||||
<xhtml:link rel="alternate" hreflang="en" href=".../?lang=en"/>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Bilingual Support** (hreflang alternate links)
|
||||
- ✅ **Priority Weighting** (1.0 for main pages, 0.9 for default)
|
||||
- ✅ **Update Frequency** (monthly for CV pages)
|
||||
- ✅ **Last Modified Date** (helps search engines)
|
||||
- ✅ **Health Endpoint** (for monitoring)
|
||||
|
||||
**SEO Impact:**
|
||||
- Faster indexing by search engines
|
||||
- Proper bilingual page discovery
|
||||
- Better crawl efficiency
|
||||
- No missed pages
|
||||
|
||||
---
|
||||
|
||||
### 6. **Robots.txt** (Crawl Instructions)
|
||||
|
||||
**Location:** `static/robots.txt`
|
||||
**URL:** `https://juan.andres.morenoyrubio.com/static/robots.txt`
|
||||
|
||||
```txt
|
||||
# robots.txt for juan.andres.morenoyrubio.com
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Disallow admin/internal paths
|
||||
Disallow: /admin/
|
||||
Disallow: /api/internal/
|
||||
Disallow: /.git/
|
||||
Disallow: /.env
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: https://juan.andres.morenoyrubio.com/static/sitemap.xml
|
||||
|
||||
# Explicit allow for major search engines
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: DuckDuckBot
|
||||
Allow: /
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Allow All** (default open access)
|
||||
- ✅ **Protect Sensitive Paths** (.git, .env, admin)
|
||||
- ✅ **Sitemap Reference** (points to sitemap.xml)
|
||||
- ✅ **Major Search Engines** (explicit allow)
|
||||
- ✅ **Future-Proof** (admin paths for future expansion)
|
||||
|
||||
**SEO Impact:**
|
||||
- Clear crawl instructions
|
||||
- Security (prevents .git exposure)
|
||||
- Sitemap discovery
|
||||
- Crawl efficiency
|
||||
|
||||
---
|
||||
|
||||
### 7. **SRI (Subresource Integrity)** (Security)
|
||||
|
||||
**Location:** `templates/index.html` (HTMX script tag)
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"
|
||||
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
|
||||
crossorigin="anonymous"></script>
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Hash Verification** (prevents CDN tampering)
|
||||
- ✅ **Cross-Origin** (CORS headers)
|
||||
- ✅ **Security Enhancement**
|
||||
|
||||
**SEO/Security Impact:**
|
||||
- Better Google ranking (security is a ranking factor)
|
||||
- Protection against CDN attacks
|
||||
- Improved trust score
|
||||
|
||||
---
|
||||
|
||||
### 8. **Resource Hints** (Performance)
|
||||
|
||||
**Location:** `templates/index.html` (Fonts section)
|
||||
|
||||
```html
|
||||
<!-- Fonts with Preload -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- ✅ **Preconnect** (establishes early connection)
|
||||
- ✅ **DNS Prefetch** (resolves DNS early)
|
||||
- ✅ **Faster Font Loading**
|
||||
|
||||
**SEO Impact:**
|
||||
- Better Core Web Vitals (performance ranking factor)
|
||||
- Faster page loads
|
||||
- Improved user experience
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Bilingual SEO Implementation
|
||||
|
||||
### Language Detection Logic
|
||||
|
||||
**English Version** (`?lang=en`):
|
||||
```html
|
||||
<html lang="en">
|
||||
<meta name="description" content="...18 years of experience in web development...">
|
||||
<meta name="keywords" content="CV, Resume, FullStack Developer...">
|
||||
<meta property="og:locale" content="en_US">
|
||||
```
|
||||
|
||||
**Spanish Version** (`?lang=es`):
|
||||
```html
|
||||
<html lang="es">
|
||||
<meta name="description" content="...18 años de experiencia en desarrollo web...">
|
||||
<meta name="keywords" content="CV, Curriculum Vitae, Desarrollador FullStack...">
|
||||
<meta property="og:locale" content="es_ES">
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Proper language targeting
|
||||
- ✅ Separate search rankings for each language
|
||||
- ✅ Correct audience targeting
|
||||
- ✅ Better local SEO (Spain, Latin America, USA)
|
||||
|
||||
---
|
||||
|
||||
## 📊 SEO Metrics & Testing
|
||||
|
||||
### Test Results:
|
||||
|
||||
#### Meta Tags ✅
|
||||
```bash
|
||||
curl http://localhost:1999/?lang=en | grep "og:title"
|
||||
# ✅ <meta property="og:title" content="Juan Andrés Moreno Rubio - Professional CV">
|
||||
```
|
||||
|
||||
#### JSON-LD Structured Data ✅
|
||||
```bash
|
||||
curl http://localhost:1999/?lang=en | grep "application/ld+json" -A20
|
||||
# ✅ Complete Person schema with all fields
|
||||
```
|
||||
|
||||
#### SRI Integrity ✅
|
||||
```bash
|
||||
curl http://localhost:1999/?lang=en | grep "integrity"
|
||||
# ✅ integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX..."
|
||||
```
|
||||
|
||||
#### Robots.txt ✅
|
||||
```bash
|
||||
curl http://localhost:1999/static/robots.txt
|
||||
# ✅ Complete robots.txt with sitemap reference
|
||||
```
|
||||
|
||||
#### Sitemap.xml ✅
|
||||
```bash
|
||||
curl http://localhost:1999/static/sitemap.xml
|
||||
# ✅ Valid XML with bilingual support
|
||||
```
|
||||
|
||||
#### Bilingual Locale ✅
|
||||
```bash
|
||||
curl http://localhost:1999/?lang=es | grep "og:locale"
|
||||
# ✅ <meta property="og:locale" content="es_ES">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Google Search Console Setup
|
||||
|
||||
After deployment, submit to Google Search Console:
|
||||
|
||||
### Step 1: Verify Ownership
|
||||
```html
|
||||
<!-- Add this to <head> if needed -->
|
||||
<meta name="google-site-verification" content="YOUR_VERIFICATION_CODE">
|
||||
```
|
||||
|
||||
### Step 2: Submit Sitemap
|
||||
```
|
||||
https://search.google.com/search-console
|
||||
→ Sitemaps → Add new sitemap
|
||||
→ https://juan.andres.morenoyrubio.com/static/sitemap.xml
|
||||
```
|
||||
|
||||
### Step 3: Request Indexing
|
||||
```
|
||||
URL Inspection → Enter page URL → Request Indexing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 SEO Validation Tools
|
||||
|
||||
### Online Validators:
|
||||
|
||||
1. **Structured Data Testing Tool**
|
||||
- URL: https://validator.schema.org/
|
||||
- Test: Paste your page URL
|
||||
- ✅ Should pass with "Person" schema
|
||||
|
||||
2. **Facebook Sharing Debugger**
|
||||
- URL: https://developers.facebook.com/tools/debug/
|
||||
- Test: Paste your page URL
|
||||
- ✅ Should show rich preview card
|
||||
|
||||
3. **LinkedIn Post Inspector**
|
||||
- URL: https://www.linkedin.com/post-inspector/
|
||||
- Test: Paste your page URL
|
||||
- ✅ Should show professional card
|
||||
|
||||
4. **Twitter Card Validator**
|
||||
- URL: https://cards-dev.twitter.com/validator
|
||||
- Test: Paste your page URL
|
||||
- ✅ Should show summary card
|
||||
|
||||
5. **Google Rich Results Test**
|
||||
- URL: https://search.google.com/test/rich-results
|
||||
- Test: Paste your page URL
|
||||
- ✅ Should detect Person schema
|
||||
|
||||
---
|
||||
|
||||
## 📈 Production Readiness Impact
|
||||
|
||||
### Previous Score: 96/100
|
||||
|
||||
**SEO:** 50/100 ⚠️
|
||||
|
||||
### New Score: **99/100** 🎉
|
||||
|
||||
**SEO:** 98/100 ✅
|
||||
|
||||
**Improvements:**
|
||||
- +48 points in SEO
|
||||
- +3 points overall production readiness
|
||||
|
||||
### Updated Breakdown:
|
||||
- **Performance:** 100/100 ✅
|
||||
- **HTMX Patterns:** 100/100 ✅
|
||||
- **Accessibility:** 85/100 ✅
|
||||
- **UX:** 95/100 ✅
|
||||
- **Error Handling:** 90/100 ✅
|
||||
- **SEO:** 98/100 ✅ (was 50/100, +48 points!)
|
||||
- **Security:** 75/100 ✅ (SRI added, +5 points)
|
||||
|
||||
**Overall:** 96% → **99%** (+3%)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Files Modified/Created
|
||||
|
||||
### Modified:
|
||||
1. **templates/index.html**
|
||||
- Replaced `<head>` section with comprehensive meta tags
|
||||
- Added Open Graph tags (11 tags)
|
||||
- Added Twitter Card tags (4 tags)
|
||||
- Added JSON-LD structured data script
|
||||
- Added SRI to HTMX script tag
|
||||
- Added resource hints (preconnect, dns-prefetch)
|
||||
- Added canonical URL
|
||||
|
||||
### Created:
|
||||
2. **static/sitemap.xml** (NEW)
|
||||
- 4 URLs (en, es, default, health)
|
||||
- Bilingual hreflang support
|
||||
- Priority weighting
|
||||
- Last modified dates
|
||||
|
||||
3. **static/robots.txt** (NEW)
|
||||
- Allow all search engines
|
||||
- Protect sensitive paths
|
||||
- Sitemap reference
|
||||
- Explicit allow for major bots
|
||||
|
||||
4. **SEO-OPTIMIZATION-COMPLETE.md** (NEW)
|
||||
- Complete documentation
|
||||
- Testing guide
|
||||
- Validation tools
|
||||
|
||||
---
|
||||
|
||||
## 🚀 SEO Checklist
|
||||
|
||||
### Technical SEO ✅
|
||||
- ✅ Primary meta tags (title, description, keywords)
|
||||
- ✅ Author attribution
|
||||
- ✅ Robots meta tag (index, follow)
|
||||
- ✅ Canonical URL
|
||||
- ✅ Language declaration (`lang` attribute)
|
||||
- ✅ Character encoding (UTF-8)
|
||||
- ✅ Viewport meta tag (mobile-friendly)
|
||||
|
||||
### Social Media SEO ✅
|
||||
- ✅ Open Graph tags (11 tags)
|
||||
- ✅ Twitter Card tags (4 tags)
|
||||
- ✅ Profile metadata (first name, last name, username)
|
||||
- ✅ Image support (profile photo)
|
||||
- ✅ Bilingual descriptions
|
||||
|
||||
### Structured Data ✅
|
||||
- ✅ JSON-LD Person schema
|
||||
- ✅ Contact information
|
||||
- ✅ Social profiles (LinkedIn, GitHub, Behance)
|
||||
- ✅ Education (universidad)
|
||||
- ✅ Skills/knowledge (8 technologies)
|
||||
- ✅ Employment (current job)
|
||||
|
||||
### Discovery & Indexing ✅
|
||||
- ✅ Sitemap.xml
|
||||
- ✅ Robots.txt
|
||||
- ✅ Sitemap reference in robots.txt
|
||||
- ✅ Bilingual URL structure
|
||||
|
||||
### Performance SEO ✅
|
||||
- ✅ Resource hints (preconnect, dns-prefetch)
|
||||
- ✅ SRI for external scripts
|
||||
- ✅ Efficient font loading
|
||||
- ✅ Cache control headers
|
||||
|
||||
### Content SEO ✅
|
||||
- ✅ Semantic HTML (`<main>`, `<header>`, `<footer>`)
|
||||
- ✅ Heading hierarchy (h1 → h2 → h3)
|
||||
- ✅ Descriptive link text
|
||||
- ✅ Alt text for images
|
||||
- ✅ Bilingual content
|
||||
|
||||
---
|
||||
|
||||
## 📝 SEO Best Practices Applied
|
||||
|
||||
### 1. **Mobile-First Indexing**
|
||||
- ✅ Responsive design
|
||||
- ✅ Viewport meta tag
|
||||
- ✅ Mobile-friendly UI
|
||||
|
||||
### 2. **Core Web Vitals**
|
||||
- ✅ Fast loading (sub-ms response)
|
||||
- ✅ Minimal JavaScript
|
||||
- ✅ Optimized fonts
|
||||
- ✅ No layout shift
|
||||
|
||||
### 3. **E-A-T (Expertise, Authoritativeness, Trustworthiness)**
|
||||
- ✅ Author attribution
|
||||
- ✅ Professional profile
|
||||
- ✅ Structured data
|
||||
- ✅ Social proof (LinkedIn, GitHub)
|
||||
|
||||
### 4. **International SEO**
|
||||
- ✅ Bilingual content
|
||||
- ✅ `hreflang` attributes in sitemap
|
||||
- ✅ Locale-specific Open Graph tags
|
||||
- ✅ Language-specific keywords
|
||||
|
||||
### 5. **Security as SEO Factor**
|
||||
- ✅ SRI for external scripts
|
||||
- ✅ Security headers (previous implementation)
|
||||
- ✅ HTTPS (production)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Keywords Ranking Strategy
|
||||
|
||||
### Primary Keywords:
|
||||
- Juan Andrés Moreno Rubio
|
||||
- Technical Consultant
|
||||
- FullStack Developer
|
||||
- SAP Customer Data Cloud
|
||||
|
||||
### Secondary Keywords:
|
||||
- React Developer
|
||||
- Node.js Developer
|
||||
- Go Developer
|
||||
- HTMX Developer
|
||||
- AI-Assisted Development
|
||||
|
||||
### Long-Tail Keywords:
|
||||
- "SAP CDC Technical Consultant Spain"
|
||||
- "FullStack Developer Canary Islands"
|
||||
- "AI-Assisted Web Development"
|
||||
- "HTMX Go Developer"
|
||||
|
||||
### Spanish Keywords:
|
||||
- Desarrollador FullStack
|
||||
- Consultor Técnico
|
||||
- Desarrollo Web
|
||||
- SAP CDC España
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Social Media Preview
|
||||
|
||||
### When Shared on Facebook/LinkedIn:
|
||||
|
||||
**Card Appearance:**
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [Profile Photo] │
|
||||
│ │
|
||||
│ Juan Andrés Moreno Rubio - │
|
||||
│ Professional CV │
|
||||
│ │
|
||||
│ Senior Technical Consultant with │
|
||||
│ 18 years of experience │
|
||||
│ │
|
||||
│ juan.andres.morenoyrubio.com │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### When Shared on Twitter/X:
|
||||
|
||||
**Card Appearance:**
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Juan Andrés Moreno Rubio - │
|
||||
│ Professional CV │
|
||||
│ │
|
||||
│ Lead Technical Consultant, │
|
||||
│ FullStack Developer │
|
||||
│ │
|
||||
│ [Profile Photo] │
|
||||
│ │
|
||||
│ juan.andres.morenoyrubio.com │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Criteria: MET
|
||||
|
||||
✅ Primary meta tags comprehensive
|
||||
✅ Open Graph tags complete (11 tags)
|
||||
✅ Twitter Card tags added (4 tags)
|
||||
✅ JSON-LD structured data implemented
|
||||
✅ Sitemap.xml created and accessible
|
||||
✅ Robots.txt created and accessible
|
||||
✅ SRI added to HTMX script
|
||||
✅ Resource hints optimized
|
||||
✅ Bilingual support in all SEO elements
|
||||
✅ All tests passing
|
||||
✅ Zero breaking changes
|
||||
|
||||
**Production Readiness:** 96% → **99%** (+3%)
|
||||
**SEO Score:** 50% → **98%** (+48%)
|
||||
**Security Score:** 70% → **75%** (+5%)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Run the Application
|
||||
|
||||
```bash
|
||||
go build -o cv-server && ./cv-server
|
||||
# Open http://localhost:1999/?lang=en
|
||||
```
|
||||
|
||||
**Verify SEO:**
|
||||
```bash
|
||||
# View meta tags
|
||||
curl http://localhost:1999/?lang=en | grep "og:"
|
||||
|
||||
# View structured data
|
||||
curl http://localhost:1999/?lang=en | grep "ld+json" -A30
|
||||
|
||||
# View robots.txt
|
||||
curl http://localhost:1999/static/robots.txt
|
||||
|
||||
# View sitemap
|
||||
curl http://localhost:1999/static/sitemap.xml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps (Optional)
|
||||
|
||||
Your CV is now **99% production-ready**!
|
||||
|
||||
**To reach 100%:**
|
||||
1. Security testing with securityheaders.com (verify all headers)
|
||||
2. Submit sitemap to Google Search Console
|
||||
3. Monitor search rankings
|
||||
4. A/B test meta descriptions for better CTR
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Complete and Production Ready
|
||||
**SEO:** World-Class Implementation
|
||||
**Next Priority:** Security Testing (optional, to reach 100%)
|
||||
Reference in New Issue
Block a user