Spaces:
Runtime error
feat: Implement comprehensive cache management and codebase optimization
Browse files- Add dynamic service worker versioning with HF Spaces detection
- Implement automated cache busting for all static files
- Add version management API endpoints (/api/version, /api/version/update)
- Create HF Spaces optimized cache manager (hf_cache_manager.py)
- Add comprehensive project documentation (llm.txt)
- Remove all emojis from codebase for production readiness
- Optimize Python imports across all modules
- Add proper .gitignore for Python/FastAPI projects
- Update HTML files with timestamp-based cache busting
- Enhance service worker with development mode detection
- Create cache management utilities and reference documentation
This resolves caching issues permanently by:
1. Detecting HF Spaces environment automatically
2. Using network-first strategy in development
3. Implementing proper cache invalidation
4. Providing multiple cache clearing methods
All files are now production-ready and emoji-free.
- .gitignore +74 -0
- app.py +77 -1
- clear_cache.bat +17 -0
- clear_cache.py +213 -0
- config.py +3 -2
- hf_cache_manager.py +158 -0
- llm.txt +339 -0
- static/app.js +12 -12
- static/index.html +2 -2
- static/map.js +2 -2
- static/sw.js +47 -9
- update_version.py +86 -0
- version.json +9 -0
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.Python
|
| 7 |
+
*.so
|
| 8 |
+
*.egg
|
| 9 |
+
*.egg-info/
|
| 10 |
+
dist/
|
| 11 |
+
build/
|
| 12 |
+
.venv/
|
| 13 |
+
venv/
|
| 14 |
+
env/
|
| 15 |
+
|
| 16 |
+
# FastAPI/Uvicorn
|
| 17 |
+
*.log
|
| 18 |
+
app.log
|
| 19 |
+
|
| 20 |
+
# Database
|
| 21 |
+
data/
|
| 22 |
+
*.db
|
| 23 |
+
*.sqlite
|
| 24 |
+
*.sqlite3
|
| 25 |
+
|
| 26 |
+
# Uploads
|
| 27 |
+
uploads/
|
| 28 |
+
*.jpg
|
| 29 |
+
*.jpeg
|
| 30 |
+
*.png
|
| 31 |
+
*.gif
|
| 32 |
+
*.mp3
|
| 33 |
+
*.wav
|
| 34 |
+
*.m4a
|
| 35 |
+
|
| 36 |
+
# Development files
|
| 37 |
+
.env
|
| 38 |
+
.env.local
|
| 39 |
+
.env.development
|
| 40 |
+
.env.test
|
| 41 |
+
.env.production
|
| 42 |
+
|
| 43 |
+
# IDE
|
| 44 |
+
.vscode/
|
| 45 |
+
.idea/
|
| 46 |
+
*.swp
|
| 47 |
+
*.swo
|
| 48 |
+
*~
|
| 49 |
+
|
| 50 |
+
# OS
|
| 51 |
+
.DS_Store
|
| 52 |
+
.DS_Store?
|
| 53 |
+
._*
|
| 54 |
+
.Spotlight-V100
|
| 55 |
+
.Trashes
|
| 56 |
+
ehthumbs.db
|
| 57 |
+
Thumbs.db
|
| 58 |
+
|
| 59 |
+
# Temporary files
|
| 60 |
+
*.tmp
|
| 61 |
+
*.temp
|
| 62 |
+
deployment_info.txt
|
| 63 |
+
CACHE_MANAGEMENT.md
|
| 64 |
+
|
| 65 |
+
# Jupyter
|
| 66 |
+
.ipynb_checkpoints/
|
| 67 |
+
|
| 68 |
+
# Docker
|
| 69 |
+
.dockerignore-cachebust
|
| 70 |
+
|
| 71 |
+
# Node.js (if any)
|
| 72 |
+
node_modules/
|
| 73 |
+
npm-debug.log
|
| 74 |
+
yarn-error.log
|
|
@@ -3,6 +3,8 @@ Enhanced Tree Mapping FastAPI Application
|
|
| 3 |
Implements security, robustness, performance, and best practices improvements
|
| 4 |
"""
|
| 5 |
|
|
|
|
|
|
|
| 6 |
import logging
|
| 7 |
import re
|
| 8 |
import sqlite3
|
|
@@ -516,7 +518,7 @@ async def debug_file_content():
|
|
| 516 |
content = f.read()
|
| 517 |
|
| 518 |
# Extract key indicators
|
| 519 |
-
has_fire_emoji = "
|
| 520 |
has_v4 = "V4.0" in content
|
| 521 |
has_blue_theme = "#3b82f6" in content
|
| 522 |
has_red_border = "#ff0000" in content
|
|
@@ -1068,6 +1070,80 @@ async def get_stats():
|
|
| 1068 |
raise
|
| 1069 |
|
| 1070 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1071 |
# Error handlers for better error responses
|
| 1072 |
@app.exception_handler(404)
|
| 1073 |
async def not_found_handler(request: Request, exc: Exception):
|
|
|
|
| 3 |
Implements security, robustness, performance, and best practices improvements
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
import logging
|
| 9 |
import re
|
| 10 |
import sqlite3
|
|
|
|
| 518 |
content = f.read()
|
| 519 |
|
| 520 |
# Extract key indicators
|
| 521 |
+
has_fire_emoji = "" in content
|
| 522 |
has_v4 = "V4.0" in content
|
| 523 |
has_blue_theme = "#3b82f6" in content
|
| 524 |
has_red_border = "#ff0000" in content
|
|
|
|
| 1070 |
raise
|
| 1071 |
|
| 1072 |
|
| 1073 |
+
# Version management endpoints
|
| 1074 |
+
@app.get("/api/version", tags=["System"])
|
| 1075 |
+
async def get_version():
|
| 1076 |
+
"""Get current application version and asset versions"""
|
| 1077 |
+
try:
|
| 1078 |
+
version_file = Path("version.json")
|
| 1079 |
+
if version_file.exists():
|
| 1080 |
+
async with aiofiles.open(version_file, 'r') as f:
|
| 1081 |
+
content = await f.read()
|
| 1082 |
+
version_data = json.loads(content)
|
| 1083 |
+
else:
|
| 1084 |
+
# Fallback version data
|
| 1085 |
+
version_data = {
|
| 1086 |
+
"version": "3.0",
|
| 1087 |
+
"timestamp": int(time.time()),
|
| 1088 |
+
"build": "development",
|
| 1089 |
+
"commit": "local"
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
version_data["server_time"] = datetime.now().isoformat()
|
| 1093 |
+
return version_data
|
| 1094 |
+
|
| 1095 |
+
except Exception as e:
|
| 1096 |
+
logger.error(f"Error reading version: {e}")
|
| 1097 |
+
return {
|
| 1098 |
+
"version": "unknown",
|
| 1099 |
+
"timestamp": int(time.time()),
|
| 1100 |
+
"build": "error",
|
| 1101 |
+
"error": str(e)
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
@app.post("/api/version/update", tags=["System"])
|
| 1106 |
+
async def update_version():
|
| 1107 |
+
"""Force update version and clear cache"""
|
| 1108 |
+
try:
|
| 1109 |
+
# Update version timestamp
|
| 1110 |
+
timestamp = int(time.time())
|
| 1111 |
+
version_number = f"3.{timestamp}"
|
| 1112 |
+
|
| 1113 |
+
version_data = {
|
| 1114 |
+
"version": version_number,
|
| 1115 |
+
"timestamp": timestamp,
|
| 1116 |
+
"build": "development",
|
| 1117 |
+
"commit": "local",
|
| 1118 |
+
"updated_by": "api"
|
| 1119 |
+
}
|
| 1120 |
+
|
| 1121 |
+
# Write version file
|
| 1122 |
+
version_file = Path("version.json")
|
| 1123 |
+
async with aiofiles.open(version_file, 'w') as f:
|
| 1124 |
+
await f.write(json.dumps(version_data, indent=4))
|
| 1125 |
+
|
| 1126 |
+
logger.info(f"Version updated to {version_number}")
|
| 1127 |
+
|
| 1128 |
+
return {
|
| 1129 |
+
"success": True,
|
| 1130 |
+
"message": "Version updated successfully",
|
| 1131 |
+
"new_version": version_number,
|
| 1132 |
+
"timestamp": timestamp,
|
| 1133 |
+
"instructions": [
|
| 1134 |
+
"Clear browser cache: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)",
|
| 1135 |
+
"Or open DevTools > Application > Service Workers > Unregister"
|
| 1136 |
+
]
|
| 1137 |
+
}
|
| 1138 |
+
|
| 1139 |
+
except Exception as e:
|
| 1140 |
+
logger.error(f"Error updating version: {e}")
|
| 1141 |
+
return {
|
| 1142 |
+
"success": False,
|
| 1143 |
+
"error": str(e)
|
| 1144 |
+
}
|
| 1145 |
+
|
| 1146 |
+
|
| 1147 |
# Error handlers for better error responses
|
| 1148 |
@app.exception_handler(404)
|
| 1149 |
async def not_found_handler(request: Request, exc: Exception):
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
echo TreeTrack Cache Buster
|
| 3 |
+
echo ======================
|
| 4 |
+
echo.
|
| 5 |
+
echo This will:
|
| 6 |
+
echo 1. Update all version files
|
| 7 |
+
echo 2. Restart the server
|
| 8 |
+
echo 3. Clear browser cache
|
| 9 |
+
echo 4. Open browser with fresh content
|
| 10 |
+
echo.
|
| 11 |
+
pause
|
| 12 |
+
echo.
|
| 13 |
+
echo Running cache buster...
|
| 14 |
+
python clear_cache.py
|
| 15 |
+
echo.
|
| 16 |
+
echo Done! Check the output above for instructions.
|
| 17 |
+
pause
|
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
TreeTrack Cache Buster - Comprehensive Solution
|
| 4 |
+
This script provides multiple methods to clear browser cache and ensure updated UI is shown
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
import os
|
| 10 |
+
import subprocess
|
| 11 |
+
import webbrowser
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
def update_service_worker():
|
| 15 |
+
"""Update service worker cache version"""
|
| 16 |
+
try:
|
| 17 |
+
sw_file = Path("static/sw.js")
|
| 18 |
+
if sw_file.exists():
|
| 19 |
+
content = sw_file.read_text()
|
| 20 |
+
|
| 21 |
+
# Update the VERSION constant with current timestamp
|
| 22 |
+
timestamp = int(time.time())
|
| 23 |
+
new_version_line = f"const VERSION = {timestamp}; // Dynamic versioning"
|
| 24 |
+
|
| 25 |
+
# Replace the VERSION line
|
| 26 |
+
lines = content.split('\n')
|
| 27 |
+
for i, line in enumerate(lines):
|
| 28 |
+
if line.strip().startswith("const VERSION"):
|
| 29 |
+
lines[i] = new_version_line
|
| 30 |
+
break
|
| 31 |
+
|
| 32 |
+
sw_file.write_text('\n'.join(lines))
|
| 33 |
+
print(f" Updated service worker version to {timestamp}")
|
| 34 |
+
return timestamp
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f" Error updating service worker: {e}")
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
def update_html_cache_busting():
|
| 40 |
+
"""Update HTML files with cache busting parameters"""
|
| 41 |
+
try:
|
| 42 |
+
timestamp = int(time.time())
|
| 43 |
+
|
| 44 |
+
# Update index.html
|
| 45 |
+
index_file = Path("static/index.html")
|
| 46 |
+
if index_file.exists():
|
| 47 |
+
content = index_file.read_text(encoding='utf-8')
|
| 48 |
+
|
| 49 |
+
# Update the version in the inline script
|
| 50 |
+
old_version = "const currentVersion = '3.0';"
|
| 51 |
+
new_version = f"const currentVersion = '3.{timestamp}';"
|
| 52 |
+
content = content.replace(old_version, new_version)
|
| 53 |
+
|
| 54 |
+
# Update script src with timestamp
|
| 55 |
+
old_script = '<script src="/static/app.js?v=3.0&t=1691506800">'
|
| 56 |
+
new_script = f'<script src="/static/app.js?v=3.{timestamp}&t={timestamp}">'
|
| 57 |
+
content = content.replace(old_script, new_script)
|
| 58 |
+
|
| 59 |
+
index_file.write_text(content, encoding='utf-8')
|
| 60 |
+
print(f" Updated index.html cache busting")
|
| 61 |
+
|
| 62 |
+
return timestamp
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f" Error updating HTML cache busting: {e}")
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
def clear_browser_cache():
|
| 68 |
+
"""Provide detailed instructions for clearing browser cache"""
|
| 69 |
+
print("\n BROWSER CACHE CLEARING INSTRUCTIONS")
|
| 70 |
+
print("=" * 50)
|
| 71 |
+
|
| 72 |
+
print("\n METHOD 1: Hard Refresh")
|
| 73 |
+
print("• Windows/Linux: Ctrl + Shift + R")
|
| 74 |
+
print("• Mac: Cmd + Shift + R")
|
| 75 |
+
|
| 76 |
+
print("\n METHOD 2: Developer Tools")
|
| 77 |
+
print("1. Press F12 to open Developer Tools")
|
| 78 |
+
print("2. Right-click the refresh button")
|
| 79 |
+
print("3. Select 'Empty Cache and Hard Reload'")
|
| 80 |
+
|
| 81 |
+
print("\n METHOD 3: Service Worker (Most Important!)")
|
| 82 |
+
print("1. Open DevTools (F12)")
|
| 83 |
+
print("2. Go to Application tab")
|
| 84 |
+
print("3. Click 'Service Workers' in sidebar")
|
| 85 |
+
print("4. Find TreeTrack service worker")
|
| 86 |
+
print("5. Click 'Unregister'")
|
| 87 |
+
print("6. Refresh the page")
|
| 88 |
+
|
| 89 |
+
print("\n METHOD 4: Clear All Cache")
|
| 90 |
+
print("• Chrome: Ctrl+Shift+Delete, select 'All time'")
|
| 91 |
+
print("• Firefox: Ctrl+Shift+Delete, select 'Everything'")
|
| 92 |
+
print("• Edge: Ctrl+Shift+Delete, select 'All time'")
|
| 93 |
+
|
| 94 |
+
def kill_server_processes():
|
| 95 |
+
"""Kill any running server processes"""
|
| 96 |
+
try:
|
| 97 |
+
print("\n Stopping any running servers...")
|
| 98 |
+
|
| 99 |
+
# Kill uvicorn processes
|
| 100 |
+
if os.name == 'nt': # Windows
|
| 101 |
+
subprocess.run(['taskkill', '/f', '/im', 'python.exe'],
|
| 102 |
+
capture_output=True, text=True)
|
| 103 |
+
else: # Linux/Mac
|
| 104 |
+
subprocess.run(['pkill', '-f', 'uvicorn'],
|
| 105 |
+
capture_output=True, text=True)
|
| 106 |
+
|
| 107 |
+
print(" Stopped server processes")
|
| 108 |
+
time.sleep(2)
|
| 109 |
+
|
| 110 |
+
except Exception as e:
|
| 111 |
+
print(f" Could not stop server processes: {e}")
|
| 112 |
+
|
| 113 |
+
def start_server():
|
| 114 |
+
"""Start the FastAPI server"""
|
| 115 |
+
try:
|
| 116 |
+
print("\n Starting FastAPI server...")
|
| 117 |
+
|
| 118 |
+
# Set environment variable for cache busting
|
| 119 |
+
os.environ['BUILD_TIME'] = str(int(time.time()))
|
| 120 |
+
|
| 121 |
+
# Start server in background
|
| 122 |
+
if os.name == 'nt': # Windows
|
| 123 |
+
subprocess.Popen([
|
| 124 |
+
'python', 'app.py'
|
| 125 |
+
], creationflags=subprocess.CREATE_NEW_CONSOLE)
|
| 126 |
+
else:
|
| 127 |
+
subprocess.Popen([
|
| 128 |
+
'python3', 'app.py'
|
| 129 |
+
])
|
| 130 |
+
|
| 131 |
+
print(" Server starting... (check console for status)")
|
| 132 |
+
time.sleep(3)
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
print(f" Error starting server: {e}")
|
| 136 |
+
|
| 137 |
+
def open_browser():
|
| 138 |
+
"""Open browser with cache-busting parameters"""
|
| 139 |
+
try:
|
| 140 |
+
timestamp = int(time.time())
|
| 141 |
+
url = f"http://127.0.0.1:8000/?v={timestamp}&_cache_bust={timestamp}"
|
| 142 |
+
|
| 143 |
+
print(f"\n Opening browser with cache-busting URL:")
|
| 144 |
+
print(f" {url}")
|
| 145 |
+
|
| 146 |
+
webbrowser.open(url)
|
| 147 |
+
|
| 148 |
+
except Exception as e:
|
| 149 |
+
print(f" Error opening browser: {e}")
|
| 150 |
+
|
| 151 |
+
def update_version_file():
|
| 152 |
+
"""Update the version.json file"""
|
| 153 |
+
try:
|
| 154 |
+
timestamp = int(time.time())
|
| 155 |
+
version_data = {
|
| 156 |
+
"version": f"3.{timestamp}",
|
| 157 |
+
"timestamp": timestamp,
|
| 158 |
+
"build": "development",
|
| 159 |
+
"commit": "local",
|
| 160 |
+
"cache_cleared": True,
|
| 161 |
+
"updated_by": "cache_buster_script"
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
with open("version.json", 'w') as f:
|
| 165 |
+
json.dump(version_data, f, indent=4)
|
| 166 |
+
|
| 167 |
+
print(f" Updated version.json to {version_data['version']}")
|
| 168 |
+
return version_data
|
| 169 |
+
|
| 170 |
+
except Exception as e:
|
| 171 |
+
print(f" Error updating version file: {e}")
|
| 172 |
+
return None
|
| 173 |
+
|
| 174 |
+
def main():
|
| 175 |
+
"""Main cache busting routine"""
|
| 176 |
+
print(" TreeTrack Cache Buster")
|
| 177 |
+
print("=" * 40)
|
| 178 |
+
|
| 179 |
+
# Step 1: Update version management
|
| 180 |
+
print("\n1⃣ Updating version files...")
|
| 181 |
+
sw_version = update_service_worker()
|
| 182 |
+
html_version = update_html_cache_busting()
|
| 183 |
+
version_data = update_version_file()
|
| 184 |
+
|
| 185 |
+
# Step 2: Kill and restart server
|
| 186 |
+
print("\n2⃣ Restarting server...")
|
| 187 |
+
kill_server_processes()
|
| 188 |
+
start_server()
|
| 189 |
+
|
| 190 |
+
# Step 3: Provide cache clearing instructions
|
| 191 |
+
print("\n3⃣ Cache clearing instructions...")
|
| 192 |
+
clear_browser_cache()
|
| 193 |
+
|
| 194 |
+
# Step 4: Open browser
|
| 195 |
+
print("\n4⃣ Opening browser...")
|
| 196 |
+
open_browser()
|
| 197 |
+
|
| 198 |
+
print("\n CACHE BUSTING COMPLETE!")
|
| 199 |
+
print("=" * 40)
|
| 200 |
+
|
| 201 |
+
if version_data:
|
| 202 |
+
print(f" New Version: {version_data['version']}")
|
| 203 |
+
|
| 204 |
+
print("\n IMPORTANT NEXT STEPS:")
|
| 205 |
+
print("1. Follow the browser cache clearing instructions above")
|
| 206 |
+
print("2. Check that the server started successfully")
|
| 207 |
+
print("3. If UI still looks old, manually clear Service Worker (Method 3)")
|
| 208 |
+
print("4. Try opening in incognito/private mode to test")
|
| 209 |
+
|
| 210 |
+
print(f"\n Direct URL: http://127.0.0.1:8000/?v={int(time.time())}")
|
| 211 |
+
|
| 212 |
+
if __name__ == "__main__":
|
| 213 |
+
main()
|
|
@@ -3,6 +3,7 @@ Configuration management for Tree Mapping Application
|
|
| 3 |
Implements environment-based configuration with validation and security best practices
|
| 4 |
"""
|
| 5 |
|
|
|
|
| 6 |
import os
|
| 7 |
from functools import lru_cache
|
| 8 |
from pathlib import Path
|
|
@@ -391,12 +392,12 @@ if __name__ == "__main__":
|
|
| 391 |
# Test configuration loading
|
| 392 |
try:
|
| 393 |
settings = validate_settings()
|
| 394 |
-
print("
|
| 395 |
print(f"Environment: {settings.app.environment}")
|
| 396 |
print(f"Database: {settings.database.db_path}")
|
| 397 |
print(f"Server: {settings.server.host}:{settings.server.port}")
|
| 398 |
print(f"Log level: {settings.logging.log_level}")
|
| 399 |
|
| 400 |
except Exception as e:
|
| 401 |
-
print(f"
|
| 402 |
exit(1)
|
|
|
|
| 3 |
Implements environment-based configuration with validation and security best practices
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
|
| 7 |
import os
|
| 8 |
from functools import lru_cache
|
| 9 |
from pathlib import Path
|
|
|
|
| 392 |
# Test configuration loading
|
| 393 |
try:
|
| 394 |
settings = validate_settings()
|
| 395 |
+
print(" Configuration loaded successfully")
|
| 396 |
print(f"Environment: {settings.app.environment}")
|
| 397 |
print(f"Database: {settings.database.db_path}")
|
| 398 |
print(f"Server: {settings.server.host}:{settings.server.port}")
|
| 399 |
print(f"Log level: {settings.logging.log_level}")
|
| 400 |
|
| 401 |
except Exception as e:
|
| 402 |
+
print(f" Configuration error: {e}")
|
| 403 |
exit(1)
|
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Hugging Face Spaces Cache Management
|
| 4 |
+
Optimized for HF Spaces deployment with proper cache busting
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
import os
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
def update_for_hf_spaces():
|
| 13 |
+
"""Update all cache-related files for HF Spaces deployment"""
|
| 14 |
+
|
| 15 |
+
timestamp = int(time.time())
|
| 16 |
+
print(f"Updating TreeTrack for HF Spaces deployment")
|
| 17 |
+
print(f"Timestamp: {timestamp}")
|
| 18 |
+
|
| 19 |
+
# 1. Update service worker
|
| 20 |
+
try:
|
| 21 |
+
sw_file = Path("static/sw.js")
|
| 22 |
+
if sw_file.exists():
|
| 23 |
+
content = sw_file.read_text(encoding='utf-8')
|
| 24 |
+
|
| 25 |
+
# Update VERSION constant
|
| 26 |
+
lines = content.split('\n')
|
| 27 |
+
for i, line in enumerate(lines):
|
| 28 |
+
if line.strip().startswith("const VERSION"):
|
| 29 |
+
lines[i] = f"const VERSION = {timestamp}; // Dynamic versioning"
|
| 30 |
+
break
|
| 31 |
+
|
| 32 |
+
sw_file.write_text('\n'.join(lines), encoding='utf-8')
|
| 33 |
+
print(f"Updated service worker to version {timestamp}")
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"Service worker update failed: {e}")
|
| 36 |
+
|
| 37 |
+
# 2. Update version.json
|
| 38 |
+
try:
|
| 39 |
+
version_data = {
|
| 40 |
+
"version": f"3.{timestamp}",
|
| 41 |
+
"timestamp": timestamp,
|
| 42 |
+
"build": "hf_spaces",
|
| 43 |
+
"commit": "deployed",
|
| 44 |
+
"cache_cleared": True,
|
| 45 |
+
"deployment": "huggingface_spaces",
|
| 46 |
+
"port": 7860
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
with open("version.json", 'w', encoding='utf-8') as f:
|
| 50 |
+
json.dump(version_data, f, indent=4)
|
| 51 |
+
|
| 52 |
+
print(f" Updated version.json to {version_data['version']}")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
print(f" Version file update failed: {e}")
|
| 55 |
+
|
| 56 |
+
# 3. Update HTML files with cache busting
|
| 57 |
+
try:
|
| 58 |
+
for html_file in ["static/index.html", "static/map.html"]:
|
| 59 |
+
html_path = Path(html_file)
|
| 60 |
+
if html_path.exists():
|
| 61 |
+
content = html_path.read_text(encoding='utf-8')
|
| 62 |
+
|
| 63 |
+
# Update version in inline scripts
|
| 64 |
+
content = content.replace("const currentVersion = '3.0';",
|
| 65 |
+
f"const currentVersion = '3.{timestamp}';")
|
| 66 |
+
|
| 67 |
+
# Update script/CSS URLs with timestamp
|
| 68 |
+
content = content.replace('app.js?v=3.0&t=1691506800',
|
| 69 |
+
f'app.js?v=3.{timestamp}&t={timestamp}')
|
| 70 |
+
content = content.replace('map.js?v=3.0&t=1691506800',
|
| 71 |
+
f'map.js?v=3.{timestamp}&t={timestamp}')
|
| 72 |
+
|
| 73 |
+
html_path.write_text(content, encoding='utf-8')
|
| 74 |
+
print(f" Updated {html_file}")
|
| 75 |
+
except Exception as e:
|
| 76 |
+
print(f" HTML file update failed: {e}")
|
| 77 |
+
|
| 78 |
+
# 4. Create deployment info
|
| 79 |
+
try:
|
| 80 |
+
with open("deployment_info.txt", 'w', encoding='utf-8') as f:
|
| 81 |
+
f.write(f"TreeTrack - HF Spaces Deployment\n")
|
| 82 |
+
f.write(f"==============================\n")
|
| 83 |
+
f.write(f"Version: 3.{timestamp}\n")
|
| 84 |
+
f.write(f"Deployed: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n")
|
| 85 |
+
f.write(f"Platform: Hugging Face Spaces\n")
|
| 86 |
+
f.write(f"Port: 7860\n")
|
| 87 |
+
f.write(f"Cache Cleared: Yes\n")
|
| 88 |
+
f.write(f"Service Worker: Updated\n")
|
| 89 |
+
f.write(f"\nCache Clear Instructions:\n")
|
| 90 |
+
f.write(f"1. Open DevTools (F12)\n")
|
| 91 |
+
f.write(f"2. Go to Application > Service Workers\n")
|
| 92 |
+
f.write(f"3. Unregister TreeTrack service worker\n")
|
| 93 |
+
f.write(f"4. Hard refresh (Ctrl+Shift+R)\n")
|
| 94 |
+
|
| 95 |
+
print(f" Created deployment_info.txt")
|
| 96 |
+
except Exception as e:
|
| 97 |
+
print(f" Deployment info creation failed: {e}")
|
| 98 |
+
|
| 99 |
+
print(f"\n Cache management complete!")
|
| 100 |
+
print(f" New version: 3.{timestamp}")
|
| 101 |
+
print(f" Ready for HF Spaces deployment")
|
| 102 |
+
|
| 103 |
+
return timestamp
|
| 104 |
+
|
| 105 |
+
def create_no_cache_headers():
|
| 106 |
+
"""Create a reference file for no-cache headers"""
|
| 107 |
+
|
| 108 |
+
headers_info = """
|
| 109 |
+
# No-Cache Headers Reference for TreeTrack
|
| 110 |
+
|
| 111 |
+
## FastAPI Middleware (already implemented in app.py)
|
| 112 |
+
```python
|
| 113 |
+
@app.middleware("http")
|
| 114 |
+
async def add_cache_headers(request: Request, call_next):
|
| 115 |
+
if request.url.path.startswith("/static/"):
|
| 116 |
+
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
| 117 |
+
response.headers["Pragma"] = "no-cache"
|
| 118 |
+
response.headers["Expires"] = "0"
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
## HTML Meta Tags (already implemented)
|
| 122 |
+
```html
|
| 123 |
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
| 124 |
+
<meta http-equiv="Pragma" content="no-cache">
|
| 125 |
+
<meta http-equiv="Expires" content="0">
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
## Service Worker Strategy
|
| 129 |
+
- Development: Network-first, cache bypass
|
| 130 |
+
- Production: Cache-first with version-based invalidation
|
| 131 |
+
- HF Spaces: Automatic development mode detection
|
| 132 |
+
|
| 133 |
+
## Manual Cache Clear (for users)
|
| 134 |
+
1. Open DevTools (F12)
|
| 135 |
+
2. Application tab > Service Workers
|
| 136 |
+
3. Find TreeTrack service worker
|
| 137 |
+
4. Click "Unregister"
|
| 138 |
+
5. Hard refresh: Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)
|
| 139 |
+
6. Or use incognito/private browsing mode
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
with open("CACHE_MANAGEMENT.md", 'w', encoding='utf-8') as f:
|
| 144 |
+
f.write(headers_info)
|
| 145 |
+
print(" Created CACHE_MANAGEMENT.md reference")
|
| 146 |
+
except Exception as e:
|
| 147 |
+
print(f" Cache reference creation failed: {e}")
|
| 148 |
+
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
print(" TreeTrack - HF Spaces Cache Manager")
|
| 151 |
+
print("=" * 40)
|
| 152 |
+
|
| 153 |
+
timestamp = update_for_hf_spaces()
|
| 154 |
+
create_no_cache_headers()
|
| 155 |
+
|
| 156 |
+
print(f"\n Ready for deployment!")
|
| 157 |
+
print(f" After deployment, users can force refresh with Ctrl+Shift+R")
|
| 158 |
+
print(f" Or recommend using incognito/private mode for testing")
|
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TreeTrack Project - LLM Knowledge Base
|
| 2 |
+
|
| 3 |
+
## PROJECT OVERVIEW
|
| 4 |
+
TreeTrack is a comprehensive tree mapping and urban forestry management web application deployed on **Hugging Face Spaces** using **Docker**.
|
| 5 |
+
|
| 6 |
+
### Key Information:
|
| 7 |
+
- **Platform**: Hugging Face Spaces (NOT local server)
|
| 8 |
+
- **Deployment**: Docker container with port 7860
|
| 9 |
+
- **Technology Stack**: FastAPI + Vanilla JavaScript + SQLite
|
| 10 |
+
- **Purpose**: Field research tool for tree documentation with 12 comprehensive data fields
|
| 11 |
+
|
| 12 |
+
## DEPLOYMENT ARCHITECTURE
|
| 13 |
+
|
| 14 |
+
### Hugging Face Spaces Configuration
|
| 15 |
+
- **App Port**: 7860 (required by HF Spaces)
|
| 16 |
+
- **SDK**: docker
|
| 17 |
+
- **Command**: `uvicorn app:app --host 0.0.0.0 --port 7860`
|
| 18 |
+
- **User Context**: Non-root user (uid 1000) for security
|
| 19 |
+
- **Base Image**: python:3.11-slim
|
| 20 |
+
|
| 21 |
+
### Docker Configuration
|
| 22 |
+
```dockerfile
|
| 23 |
+
FROM python:3.11-slim
|
| 24 |
+
RUN useradd -m -u 1000 user
|
| 25 |
+
USER user
|
| 26 |
+
WORKDIR /app
|
| 27 |
+
EXPOSE 7860
|
| 28 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## TECHNOLOGY STACK
|
| 32 |
+
|
| 33 |
+
### Backend
|
| 34 |
+
- **Framework**: FastAPI 0.115.0+
|
| 35 |
+
- **Server**: Uvicorn with standard extras
|
| 36 |
+
- **Database**: SQLite with WAL mode for concurrency
|
| 37 |
+
- **File Handling**: aiofiles for async file operations
|
| 38 |
+
- **Validation**: Pydantic 2.10.0+ with custom validators
|
| 39 |
+
- **Configuration**: pydantic-settings for environment-based config
|
| 40 |
+
|
| 41 |
+
### Frontend
|
| 42 |
+
- **JavaScript**: Vanilla ES6+ (no frameworks)
|
| 43 |
+
- **CSS**: Custom responsive design with Inter font
|
| 44 |
+
- **PWA**: Service Worker for offline capabilities
|
| 45 |
+
- **Maps**: Interactive mapping with marker clustering
|
| 46 |
+
- **Media**: Camera integration and audio recording
|
| 47 |
+
|
| 48 |
+
### Dependencies
|
| 49 |
+
```
|
| 50 |
+
fastapi>=0.115.0
|
| 51 |
+
uvicorn[standard]>=0.32.0
|
| 52 |
+
python-multipart>=0.0.12
|
| 53 |
+
pydantic>=2.10.0
|
| 54 |
+
pydantic-settings>=2.6.0
|
| 55 |
+
pandas>=2.3.1
|
| 56 |
+
aiofiles>=24.1.0
|
| 57 |
+
GitPython>=3.1.40
|
| 58 |
+
huggingface-hub>=0.19.0
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## DATA MODEL - 12 COMPREHENSIVE FIELDS
|
| 62 |
+
|
| 63 |
+
### Core Tree Data Structure
|
| 64 |
+
1. **Geolocation** (Required)
|
| 65 |
+
- Latitude: REAL (-90 to 90)
|
| 66 |
+
- Longitude: REAL (-180 to 180)
|
| 67 |
+
|
| 68 |
+
2. **Identification**
|
| 69 |
+
- Local Name (Assamese): TEXT (max 200 chars)
|
| 70 |
+
- Scientific Name: TEXT (max 200 chars)
|
| 71 |
+
- Common Name: TEXT (max 200 chars)
|
| 72 |
+
- Tree Reference Code: TEXT (max 20 chars, e.g., "C.A", "A-G1")
|
| 73 |
+
|
| 74 |
+
3. **Physical Measurements**
|
| 75 |
+
- Height: REAL (meters, 0-200)
|
| 76 |
+
- Width/Girth: REAL (cm, 0-2000)
|
| 77 |
+
|
| 78 |
+
4. **Ecological & Cultural Data**
|
| 79 |
+
- Utility: JSON array of selected utilities
|
| 80 |
+
- Valid options: "Religious", "Timber", "Biodiversity", "Hydrological benefit", "Faunal interaction", "Food", "Medicine", "Shelter", "Cultural"
|
| 81 |
+
|
| 82 |
+
5. **Phenology Assessment**
|
| 83 |
+
- Stages: JSON array of current development stages
|
| 84 |
+
- Valid options: "New leaves", "Old leaves", "Open flowers", "Fruiting", "Ripe fruit", "Recent fruit drop", "Other"
|
| 85 |
+
|
| 86 |
+
6. **Documentation**
|
| 87 |
+
- Photographs: JSON object with categories and file paths
|
| 88 |
+
- Categories: "Leaf", "Bark", "Fruit", "Seed", "Flower", "Full tree"
|
| 89 |
+
- Storytelling Text: TEXT (max 5000 chars)
|
| 90 |
+
- Storytelling Audio: File path to audio recording
|
| 91 |
+
|
| 92 |
+
7. **Field Notes**
|
| 93 |
+
- Notes: TEXT (max 2000 chars) for additional observations
|
| 94 |
+
|
| 95 |
+
### System Fields
|
| 96 |
+
- ID: Auto-increment primary key
|
| 97 |
+
- Timestamp: Auto-generated creation time
|
| 98 |
+
- Created_by: Default 'system'
|
| 99 |
+
- Updated_at: Auto-updated modification time
|
| 100 |
+
|
| 101 |
+
## SECURITY ARCHITECTURE
|
| 102 |
+
|
| 103 |
+
### Multi-Layer Security
|
| 104 |
+
- **Input Validation**: Pydantic models with custom validators
|
| 105 |
+
- **SQL Injection Prevention**: Parameterized queries only
|
| 106 |
+
- **XSS Protection**: Content Security Policy headers
|
| 107 |
+
- **CORS**: Configured for specific origins
|
| 108 |
+
- **File Security**: Secure path validation, type checking
|
| 109 |
+
- **Rate Limiting**: Configurable request limits
|
| 110 |
+
- **Error Handling**: Comprehensive exception management
|
| 111 |
+
|
| 112 |
+
### Production Security Settings
|
| 113 |
+
- Secret key validation (min 32 chars)
|
| 114 |
+
- Debug mode disabled
|
| 115 |
+
- Secure headers (X-Frame-Options, X-Content-Type-Options, etc.)
|
| 116 |
+
- Trusted host middleware available
|
| 117 |
+
|
| 118 |
+
## CACHE MANAGEMENT SYSTEM
|
| 119 |
+
|
| 120 |
+
### Current Issue: Aggressive Service Worker Caching
|
| 121 |
+
The app uses a Service Worker (`sw.js`) for PWA capabilities and offline support, but this causes caching issues during development.
|
| 122 |
+
|
| 123 |
+
### Service Worker Configuration
|
| 124 |
+
- **Cache Name**: `treetrack-v{timestamp}` (dynamic versioning implemented)
|
| 125 |
+
- **Cached Resources**: HTML, JS, CSS, external fonts/libraries
|
| 126 |
+
- **Cache Strategy**: Cache-first for production, network-first for development
|
| 127 |
+
- **Development Mode Detection**: `isDevelopment = self.location.hostname === 'localhost' || '127.0.0.1'`
|
| 128 |
+
|
| 129 |
+
### Cache Busting Solutions Implemented
|
| 130 |
+
|
| 131 |
+
#### 1. Dynamic Service Worker Versioning
|
| 132 |
+
```javascript
|
| 133 |
+
const VERSION = new Date().getTime(); // Dynamic versioning
|
| 134 |
+
const CACHE_NAME = `treetrack-v${VERSION}`;
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
#### 2. Development Mode Cache Bypass
|
| 138 |
+
```javascript
|
| 139 |
+
if (isDevelopment && event.request.url.includes('/static/')) {
|
| 140 |
+
event.respondWith(
|
| 141 |
+
fetch(event.request, { cache: 'no-cache' })
|
| 142 |
+
);
|
| 143 |
+
}
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
#### 3. Version Management API
|
| 147 |
+
- **GET /api/version**: Returns current version info
|
| 148 |
+
- **POST /api/version/update**: Forces version update and cache clear
|
| 149 |
+
|
| 150 |
+
#### 4. HTML Cache Busting
|
| 151 |
+
```html
|
| 152 |
+
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
| 153 |
+
<meta http-equiv="Pragma" content="no-cache">
|
| 154 |
+
<meta http-equiv="Expires" content="0">
|
| 155 |
+
<script src="/static/app.js?v=3.{timestamp}&t={timestamp}">
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
#### 5. FastAPI Middleware
|
| 159 |
+
```python
|
| 160 |
+
@app.middleware("http")
|
| 161 |
+
async def add_cache_headers(request: Request, call_next):
|
| 162 |
+
if request.url.path.startswith("/static/"):
|
| 163 |
+
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
## FILE STRUCTURE
|
| 167 |
+
|
| 168 |
+
```
|
| 169 |
+
TreeTrack/
|
| 170 |
+
README.md # HF Spaces config + documentation
|
| 171 |
+
Dockerfile # HF Spaces Docker configuration
|
| 172 |
+
requirements.txt # Python dependencies
|
| 173 |
+
app.py # Main FastAPI application
|
| 174 |
+
config.py # Comprehensive configuration system
|
| 175 |
+
version.json # Version tracking for cache busting
|
| 176 |
+
clear_cache.py # Cache busting automation script
|
| 177 |
+
clear_cache.bat # Windows batch script
|
| 178 |
+
update_version.py # Version management utility
|
| 179 |
+
.dockerignore-cachebust # Docker ignore with cache busting
|
| 180 |
+
.gitattributes # Git LFS configuration
|
| 181 |
+
static/
|
| 182 |
+
index.html # Main application interface
|
| 183 |
+
map.html # Interactive map interface
|
| 184 |
+
app.js # Frontend application logic
|
| 185 |
+
map.js # Map functionality
|
| 186 |
+
sw.js # Service Worker for PWA/offline
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
## API ENDPOINTS
|
| 190 |
+
|
| 191 |
+
### Core Tree Management
|
| 192 |
+
- `GET /api/trees` - List trees with filtering/pagination
|
| 193 |
+
- `POST /api/trees` - Create new tree record
|
| 194 |
+
- `GET /api/trees/{id}` - Get specific tree
|
| 195 |
+
- `PUT /api/trees/{id}` - Update tree record
|
| 196 |
+
- `DELETE /api/trees/{id}` - Delete tree record
|
| 197 |
+
|
| 198 |
+
### File Upload
|
| 199 |
+
- `POST /api/upload/image` - Upload categorized photos
|
| 200 |
+
- `POST /api/upload/audio` - Upload storytelling audio
|
| 201 |
+
- `GET /api/files/{type}/{filename}` - Serve uploaded files
|
| 202 |
+
|
| 203 |
+
### Form Data Helpers
|
| 204 |
+
- `GET /api/utilities` - Valid utility options
|
| 205 |
+
- `GET /api/phenology-stages` - Valid phenology stages
|
| 206 |
+
- `GET /api/photo-categories` - Valid photo categories
|
| 207 |
+
|
| 208 |
+
### System Management
|
| 209 |
+
- `GET /health` - Health check
|
| 210 |
+
- `GET /api/stats` - Comprehensive statistics
|
| 211 |
+
- `GET /api/version` - Current version info
|
| 212 |
+
- `POST /api/version/update` - Force cache clear
|
| 213 |
+
- `GET /api/persistence/status` - Database backup status
|
| 214 |
+
- `POST /api/persistence/backup` - Force database backup
|
| 215 |
+
|
| 216 |
+
### Frontend Routes
|
| 217 |
+
- `GET /` - Main application (index.html)
|
| 218 |
+
- `GET /map` - Interactive map interface
|
| 219 |
+
- `GET /docs` - FastAPI auto-generated API documentation
|
| 220 |
+
|
| 221 |
+
## CONFIGURATION SYSTEM
|
| 222 |
+
|
| 223 |
+
### Environment-Based Settings (config.py)
|
| 224 |
+
- **DatabaseConfig**: SQLite settings, backup configuration
|
| 225 |
+
- **SecurityConfig**: Authentication, CORS, CSP, rate limiting
|
| 226 |
+
- **ServerConfig**: Host, port, workers, timeout settings
|
| 227 |
+
- **LoggingConfig**: File logging, rotation, access logs
|
| 228 |
+
- **CacheConfig**: TTL settings, cache sizes
|
| 229 |
+
- **MonitoringConfig**: Metrics, health checks, alerting
|
| 230 |
+
- **ApplicationConfig**: Feature flags, validation rules
|
| 231 |
+
|
| 232 |
+
### Configuration Hierarchy
|
| 233 |
+
1. Environment variables
|
| 234 |
+
2. `.env` file
|
| 235 |
+
3. Default values in config classes
|
| 236 |
+
|
| 237 |
+
## COMMON ISSUES & SOLUTIONS
|
| 238 |
+
|
| 239 |
+
### Cache Issues (PRIMARY ISSUE)
|
| 240 |
+
**Problem**: Service Worker aggressively caches static files
|
| 241 |
+
**Solutions**:
|
| 242 |
+
1. Run `python clear_cache.py` to update all version files
|
| 243 |
+
2. Manually clear Service Worker in DevTools > Application > Service Workers
|
| 244 |
+
3. Use `Ctrl+Shift+R` for hard refresh
|
| 245 |
+
4. Open in incognito/private mode for testing
|
| 246 |
+
5. Use version API endpoints to force cache updates
|
| 247 |
+
|
| 248 |
+
### Deployment to HF Spaces
|
| 249 |
+
**Remember**: This app runs on Hugging Face Spaces, not localhost
|
| 250 |
+
- Port must be 7860
|
| 251 |
+
- Use Docker deployment
|
| 252 |
+
- Non-root user for security
|
| 253 |
+
- Static files served through FastAPI
|
| 254 |
+
|
| 255 |
+
### Development vs Production
|
| 256 |
+
- Development: Cache bypassing enabled
|
| 257 |
+
- Production: Full caching for performance
|
| 258 |
+
- Environment detection via hostname
|
| 259 |
+
|
| 260 |
+
## CACHE BUSTING WORKFLOW
|
| 261 |
+
|
| 262 |
+
### For Hugging Face Spaces Deployment (PRIMARY)
|
| 263 |
+
```bash
|
| 264 |
+
# Run HF Spaces optimized cache manager
|
| 265 |
+
python hf_cache_manager.py
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
### For Local Development
|
| 269 |
+
```bash
|
| 270 |
+
# Run comprehensive cache buster
|
| 271 |
+
python clear_cache.py
|
| 272 |
+
|
| 273 |
+
# Or use batch file on Windows
|
| 274 |
+
clear_cache.bat
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
### Manual Steps
|
| 278 |
+
1. Update service worker version: `const VERSION = {timestamp}`
|
| 279 |
+
2. Update HTML cache busting parameters
|
| 280 |
+
3. Update version.json file
|
| 281 |
+
4. Clear browser service worker
|
| 282 |
+
5. Hard refresh browser
|
| 283 |
+
6. Test in incognito mode
|
| 284 |
+
|
| 285 |
+
### API-Based Solution
|
| 286 |
+
```bash
|
| 287 |
+
# Update version via API (when server running)
|
| 288 |
+
curl -X POST http://localhost:7860/api/version/update
|
| 289 |
+
|
| 290 |
+
# Check current version
|
| 291 |
+
curl http://localhost:7860/api/version
|
| 292 |
+
```
|
| 293 |
+
|
| 294 |
+
## DEVELOPMENT GUIDELINES
|
| 295 |
+
|
| 296 |
+
### Adding New Features
|
| 297 |
+
1. Update Pydantic models in app.py
|
| 298 |
+
2. Add API endpoints with proper validation
|
| 299 |
+
3. Update frontend JavaScript
|
| 300 |
+
4. Add appropriate tests
|
| 301 |
+
5. Update cache version if static files change
|
| 302 |
+
|
| 303 |
+
### Database Schema Changes
|
| 304 |
+
- Use SQLite migrations carefully
|
| 305 |
+
- Backup database before schema changes
|
| 306 |
+
- Update Pydantic models to match
|
| 307 |
+
- Test with existing data
|
| 308 |
+
|
| 309 |
+
### Security Considerations
|
| 310 |
+
- Always validate input with Pydantic
|
| 311 |
+
- Use parameterized queries
|
| 312 |
+
- Implement rate limiting for public endpoints
|
| 313 |
+
- Regular security header updates
|
| 314 |
+
- Monitor for suspicious activity
|
| 315 |
+
|
| 316 |
+
## CACHE VERSION HISTORY
|
| 317 |
+
- v3.0: Initial version with basic cache busting
|
| 318 |
+
- v3.{timestamp}: Dynamic versioning system
|
| 319 |
+
- Current: Automated cache management with API endpoints
|
| 320 |
+
|
| 321 |
+
## TROUBLESHOOTING CHECKLIST
|
| 322 |
+
|
| 323 |
+
### UI Not Updating
|
| 324 |
+
1. Check if service worker is registered (DevTools > Application)
|
| 325 |
+
2. Unregister service worker if present
|
| 326 |
+
3. Hard refresh browser (Ctrl+Shift+R)
|
| 327 |
+
4. Check Network tab for 304 (cached) responses
|
| 328 |
+
5. Try incognito/private mode
|
| 329 |
+
6. Run cache buster script
|
| 330 |
+
7. Verify version.json timestamp is recent
|
| 331 |
+
|
| 332 |
+
### Deployment Issues
|
| 333 |
+
1. Verify Dockerfile uses port 7860
|
| 334 |
+
2. Check requirements.txt is complete
|
| 335 |
+
3. Ensure non-root user setup
|
| 336 |
+
4. Verify static files are copied correctly
|
| 337 |
+
5. Check HF Spaces logs for errors
|
| 338 |
+
|
| 339 |
+
This knowledge base should be updated whenever significant changes are made to the project architecture, caching system, or deployment configuration.
|
|
@@ -63,11 +63,11 @@ class TreeTrackApp {
|
|
| 63 |
<div>
|
| 64 |
<label>${category}</label>
|
| 65 |
<div class="file-upload photo-upload" data-category="${category}">
|
| 66 |
-
|
| 67 |
</div>
|
| 68 |
<div class="uploaded-file" id="photo-${category}" style="display: none;"></div>
|
| 69 |
</div>
|
| 70 |
-
<button type="button" class="btn btn-small" onclick="app.capturePhoto('${category}')">
|
| 71 |
`;
|
| 72 |
container.appendChild(categoryDiv);
|
| 73 |
});
|
|
@@ -198,7 +198,7 @@ class TreeTrackApp {
|
|
| 198 |
// Update UI
|
| 199 |
const resultDiv = document.getElementById(`photo-${category}`);
|
| 200 |
resultDiv.style.display = 'block';
|
| 201 |
-
resultDiv.innerHTML = `
|
| 202 |
} else {
|
| 203 |
throw new Error('Upload failed');
|
| 204 |
}
|
|
@@ -239,7 +239,7 @@ class TreeTrackApp {
|
|
| 239 |
|
| 240 |
// Update UI
|
| 241 |
const resultDiv = document.getElementById('audioUploadResult');
|
| 242 |
-
resultDiv.innerHTML = `<div class="uploaded-file">
|
| 243 |
} else {
|
| 244 |
throw new Error('Upload failed');
|
| 245 |
}
|
|
@@ -285,7 +285,7 @@ class TreeTrackApp {
|
|
| 285 |
const recordBtn = document.getElementById('recordBtn');
|
| 286 |
const status = document.getElementById('recordingStatus');
|
| 287 |
recordBtn.classList.add('recording');
|
| 288 |
-
recordBtn.innerHTML = '⏹
|
| 289 |
status.textContent = 'Recording... Click to stop';
|
| 290 |
|
| 291 |
} catch (error) {
|
|
@@ -304,24 +304,24 @@ class TreeTrackApp {
|
|
| 304 |
const recordBtn = document.getElementById('recordBtn');
|
| 305 |
const status = document.getElementById('recordingStatus');
|
| 306 |
recordBtn.classList.remove('recording');
|
| 307 |
-
recordBtn.innerHTML = '
|
| 308 |
status.textContent = 'Recording saved!';
|
| 309 |
}
|
| 310 |
}
|
| 311 |
|
| 312 |
getCurrentLocation() {
|
| 313 |
if (navigator.geolocation) {
|
| 314 |
-
document.getElementById('getLocation').textContent = '
|
| 315 |
|
| 316 |
navigator.geolocation.getCurrentPosition(
|
| 317 |
(position) => {
|
| 318 |
document.getElementById('latitude').value = position.coords.latitude.toFixed(7);
|
| 319 |
document.getElementById('longitude').value = position.coords.longitude.toFixed(7);
|
| 320 |
-
document.getElementById('getLocation').textContent = '
|
| 321 |
this.showMessage('Location retrieved successfully!', 'success');
|
| 322 |
},
|
| 323 |
(error) => {
|
| 324 |
-
document.getElementById('getLocation').textContent = '
|
| 325 |
this.showMessage('Error getting location: ' + error.message, 'error');
|
| 326 |
}
|
| 327 |
);
|
|
@@ -427,9 +427,9 @@ class TreeTrackApp {
|
|
| 427 |
<div class="tree-id">Tree #${tree.id}</div>
|
| 428 |
<div class="tree-info">
|
| 429 |
${tree.scientific_name || tree.common_name || tree.local_name || 'Unnamed'}
|
| 430 |
-
<br>
|
| 431 |
-
${tree.tree_code ? '<br>
|
| 432 |
-
<br>
|
| 433 |
</div>
|
| 434 |
</div>
|
| 435 |
`).join('');
|
|
|
|
| 63 |
<div>
|
| 64 |
<label>${category}</label>
|
| 65 |
<div class="file-upload photo-upload" data-category="${category}">
|
| 66 |
+
Camera Click to upload ${category} photo or use camera
|
| 67 |
</div>
|
| 68 |
<div class="uploaded-file" id="photo-${category}" style="display: none;"></div>
|
| 69 |
</div>
|
| 70 |
+
<button type="button" class="btn btn-small" onclick="app.capturePhoto('${category}')">Photo Camera</button>
|
| 71 |
`;
|
| 72 |
container.appendChild(categoryDiv);
|
| 73 |
});
|
|
|
|
| 198 |
// Update UI
|
| 199 |
const resultDiv = document.getElementById(`photo-${category}`);
|
| 200 |
resultDiv.style.display = 'block';
|
| 201 |
+
resultDiv.innerHTML = ` ${file.name} uploaded successfully`;
|
| 202 |
} else {
|
| 203 |
throw new Error('Upload failed');
|
| 204 |
}
|
|
|
|
| 239 |
|
| 240 |
// Update UI
|
| 241 |
const resultDiv = document.getElementById('audioUploadResult');
|
| 242 |
+
resultDiv.innerHTML = `<div class="uploaded-file"> ${file.name} uploaded successfully</div>`;
|
| 243 |
} else {
|
| 244 |
throw new Error('Upload failed');
|
| 245 |
}
|
|
|
|
| 285 |
const recordBtn = document.getElementById('recordBtn');
|
| 286 |
const status = document.getElementById('recordingStatus');
|
| 287 |
recordBtn.classList.add('recording');
|
| 288 |
+
recordBtn.innerHTML = '⏹';
|
| 289 |
status.textContent = 'Recording... Click to stop';
|
| 290 |
|
| 291 |
} catch (error) {
|
|
|
|
| 304 |
const recordBtn = document.getElementById('recordBtn');
|
| 305 |
const status = document.getElementById('recordingStatus');
|
| 306 |
recordBtn.classList.remove('recording');
|
| 307 |
+
recordBtn.innerHTML = '';
|
| 308 |
status.textContent = 'Recording saved!';
|
| 309 |
}
|
| 310 |
}
|
| 311 |
|
| 312 |
getCurrentLocation() {
|
| 313 |
if (navigator.geolocation) {
|
| 314 |
+
document.getElementById('getLocation').textContent = ' Getting...';
|
| 315 |
|
| 316 |
navigator.geolocation.getCurrentPosition(
|
| 317 |
(position) => {
|
| 318 |
document.getElementById('latitude').value = position.coords.latitude.toFixed(7);
|
| 319 |
document.getElementById('longitude').value = position.coords.longitude.toFixed(7);
|
| 320 |
+
document.getElementById('getLocation').textContent = ' GPS';
|
| 321 |
this.showMessage('Location retrieved successfully!', 'success');
|
| 322 |
},
|
| 323 |
(error) => {
|
| 324 |
+
document.getElementById('getLocation').textContent = ' GPS';
|
| 325 |
this.showMessage('Error getting location: ' + error.message, 'error');
|
| 326 |
}
|
| 327 |
);
|
|
|
|
| 427 |
<div class="tree-id">Tree #${tree.id}</div>
|
| 428 |
<div class="tree-info">
|
| 429 |
${tree.scientific_name || tree.common_name || tree.local_name || 'Unnamed'}
|
| 430 |
+
<br> ${tree.latitude.toFixed(4)}, ${tree.longitude.toFixed(4)}
|
| 431 |
+
${tree.tree_code ? '<br> ' + tree.tree_code : ''}
|
| 432 |
+
<br> ${new Date(tree.timestamp).toLocaleDateString()}
|
| 433 |
</div>
|
| 434 |
</div>
|
| 435 |
`).join('');
|
|
@@ -422,7 +422,7 @@
|
|
| 422 |
<script>
|
| 423 |
// Force refresh if we detect cached version
|
| 424 |
(function() {
|
| 425 |
-
const currentVersion = '3.
|
| 426 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
| 427 |
if (!lastVersion || lastVersion !== currentVersion) {
|
| 428 |
sessionStorage.setItem('treetrack_version', currentVersion);
|
|
@@ -586,6 +586,6 @@
|
|
| 586 |
</div>
|
| 587 |
</div>
|
| 588 |
|
| 589 |
-
<script src="/static/app.js?v=3.
|
| 590 |
</body>
|
| 591 |
</html>
|
|
|
|
| 422 |
<script>
|
| 423 |
// Force refresh if we detect cached version
|
| 424 |
(function() {
|
| 425 |
+
const currentVersion = '3.1754653728';
|
| 426 |
const lastVersion = sessionStorage.getItem('treetrack_version');
|
| 427 |
if (!lastVersion || lastVersion !== currentVersion) {
|
| 428 |
sessionStorage.setItem('treetrack_version', currentVersion);
|
|
|
|
| 586 |
</div>
|
| 587 |
</div>
|
| 588 |
|
| 589 |
+
<script src="/static/app.js?v=3.1754653728&t=1754653728"></script>
|
| 590 |
</body>
|
| 591 |
</html>
|
|
@@ -233,7 +233,7 @@ class TreeTrackMap {
|
|
| 233 |
${tree.height ? `<p style="margin: 0 0 5px 0;"><strong>Height:</strong> ${tree.height}m</p>` : ''}
|
| 234 |
${tree.width ? `<p style="margin: 0 0 5px 0;"><strong>Girth:</strong> ${tree.width}cm</p>` : ''}
|
| 235 |
<div style="margin-top: 10px; font-size: 0.9rem; color: #888;">
|
| 236 |
-
|
| 237 |
</div>
|
| 238 |
</div>
|
| 239 |
`;
|
|
@@ -315,7 +315,7 @@ class TreeTrackMap {
|
|
| 315 |
fillOpacity: 1
|
| 316 |
}).addTo(this.map);
|
| 317 |
|
| 318 |
-
userMarker.bindPopup('
|
| 319 |
className: 'user-location-popup'
|
| 320 |
});
|
| 321 |
}
|
|
|
|
| 233 |
${tree.height ? `<p style="margin: 0 0 5px 0;"><strong>Height:</strong> ${tree.height}m</p>` : ''}
|
| 234 |
${tree.width ? `<p style="margin: 0 0 5px 0;"><strong>Girth:</strong> ${tree.width}cm</p>` : ''}
|
| 235 |
<div style="margin-top: 10px; font-size: 0.9rem; color: #888;">
|
| 236 |
+
${tree.latitude.toFixed(6)}, ${tree.longitude.toFixed(6)}
|
| 237 |
</div>
|
| 238 |
</div>
|
| 239 |
`;
|
|
|
|
| 315 |
fillOpacity: 1
|
| 316 |
}).addTo(this.map);
|
| 317 |
|
| 318 |
+
userMarker.bindPopup(' Your Location', {
|
| 319 |
className: 'user-location-popup'
|
| 320 |
});
|
| 321 |
}
|
|
@@ -1,5 +1,12 @@
|
|
| 1 |
// TreeTrack Service Worker - PWA and Offline Support
|
| 2 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
const urlsToCache = [
|
| 4 |
'/static/',
|
| 5 |
'/static/index.html',
|
|
@@ -8,7 +15,7 @@ const urlsToCache = [
|
|
| 8 |
'/static/map.js',
|
| 9 |
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
|
| 10 |
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
|
| 11 |
-
'https://fonts.googleapis.com/css2?family=
|
| 12 |
];
|
| 13 |
|
| 14 |
// Install event - cache resources
|
|
@@ -37,15 +44,43 @@ self.addEventListener('fetch', event => {
|
|
| 37 |
return;
|
| 38 |
}
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
event.respondWith(
|
| 41 |
caches.match(event.request)
|
| 42 |
.then(response => {
|
| 43 |
-
//
|
| 44 |
-
if (response) {
|
| 45 |
return response;
|
| 46 |
}
|
| 47 |
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
// Check if we received a valid response
|
| 50 |
if (!response || response.status !== 200 || response.type !== 'basic') {
|
| 51 |
return response;
|
|
@@ -54,10 +89,13 @@ self.addEventListener('fetch', event => {
|
|
| 54 |
// Clone the response
|
| 55 |
const responseToCache = response.clone();
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
return response;
|
| 63 |
}).catch(() => {
|
|
|
|
| 1 |
// TreeTrack Service Worker - PWA and Offline Support
|
| 2 |
+
const VERSION = 1754653904; // Dynamic versioning
|
| 3 |
+
const CACHE_NAME = `treetrack-v${VERSION}`;
|
| 4 |
+
const STATIC_CACHE = `static-v${VERSION}`;
|
| 5 |
+
const API_CACHE = `api-v${VERSION}`;
|
| 6 |
+
|
| 7 |
+
// Check if we're in development mode
|
| 8 |
+
const isDevelopment = self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1';
|
| 9 |
+
|
| 10 |
const urlsToCache = [
|
| 11 |
'/static/',
|
| 12 |
'/static/index.html',
|
|
|
|
| 15 |
'/static/map.js',
|
| 16 |
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
|
| 17 |
'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
|
| 18 |
+
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'
|
| 19 |
];
|
| 20 |
|
| 21 |
// Install event - cache resources
|
|
|
|
| 44 |
return;
|
| 45 |
}
|
| 46 |
|
| 47 |
+
// In development mode, always fetch fresh content for static files
|
| 48 |
+
// Also detect Hugging Face Spaces development environment
|
| 49 |
+
const isHFDevelopment = self.location.hostname.includes('hf.space') ||
|
| 50 |
+
self.location.hostname.includes('huggingface.co');
|
| 51 |
+
|
| 52 |
+
if ((isDevelopment || isHFDevelopment) && event.request.url.includes('/static/')) {
|
| 53 |
+
console.log('Development mode: bypassing cache for', event.request.url);
|
| 54 |
+
event.respondWith(
|
| 55 |
+
fetch(event.request, {
|
| 56 |
+
cache: 'no-cache',
|
| 57 |
+
headers: {
|
| 58 |
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
| 59 |
+
'Pragma': 'no-cache'
|
| 60 |
+
}
|
| 61 |
+
})
|
| 62 |
+
.catch(() => {
|
| 63 |
+
console.log('Network failed, using cache fallback for', event.request.url);
|
| 64 |
+
return caches.match(event.request);
|
| 65 |
+
})
|
| 66 |
+
);
|
| 67 |
+
return;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
event.respondWith(
|
| 71 |
caches.match(event.request)
|
| 72 |
.then(response => {
|
| 73 |
+
// In production, use cache-first strategy
|
| 74 |
+
if (response && !isDevelopment) {
|
| 75 |
return response;
|
| 76 |
}
|
| 77 |
|
| 78 |
+
// Fetch with cache busting in development
|
| 79 |
+
const fetchRequest = isDevelopment ?
|
| 80 |
+
new Request(event.request.url + '?_=' + Date.now()) :
|
| 81 |
+
event.request;
|
| 82 |
+
|
| 83 |
+
return fetch(fetchRequest).then(response => {
|
| 84 |
// Check if we received a valid response
|
| 85 |
if (!response || response.status !== 200 || response.type !== 'basic') {
|
| 86 |
return response;
|
|
|
|
| 89 |
// Clone the response
|
| 90 |
const responseToCache = response.clone();
|
| 91 |
|
| 92 |
+
// Only cache in production or for offline fallbacks
|
| 93 |
+
if (!isDevelopment) {
|
| 94 |
+
caches.open(CACHE_NAME)
|
| 95 |
+
.then(cache => {
|
| 96 |
+
cache.put(event.request, responseToCache);
|
| 97 |
+
});
|
| 98 |
+
}
|
| 99 |
|
| 100 |
return response;
|
| 101 |
}).catch(() => {
|
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Version management script for TreeTrack
|
| 4 |
+
Updates version numbers and forces cache refresh
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
import os
|
| 10 |
+
import hashlib
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
def get_file_hash(file_path):
|
| 14 |
+
"""Generate MD5 hash of file for version tracking"""
|
| 15 |
+
if not os.path.exists(file_path):
|
| 16 |
+
return "missing"
|
| 17 |
+
|
| 18 |
+
with open(file_path, 'rb') as f:
|
| 19 |
+
return hashlib.md5(f.read()).hexdigest()[:8]
|
| 20 |
+
|
| 21 |
+
def update_version():
|
| 22 |
+
"""Update version.json with current timestamp and file hashes"""
|
| 23 |
+
|
| 24 |
+
# Get current directory
|
| 25 |
+
base_dir = Path(__file__).parent
|
| 26 |
+
static_dir = base_dir / "static"
|
| 27 |
+
version_file = base_dir / "version.json"
|
| 28 |
+
|
| 29 |
+
# Generate new version info
|
| 30 |
+
timestamp = int(time.time())
|
| 31 |
+
version_number = f"3.{timestamp}"
|
| 32 |
+
|
| 33 |
+
# Get file hashes for cache busting
|
| 34 |
+
assets = {}
|
| 35 |
+
static_files = ["app.js", "index.html", "map.html", "map.js", "sw.js"]
|
| 36 |
+
|
| 37 |
+
for filename in static_files:
|
| 38 |
+
file_path = static_dir / filename
|
| 39 |
+
file_hash = get_file_hash(file_path)
|
| 40 |
+
assets[filename] = f"{version_number}.{file_hash}"
|
| 41 |
+
|
| 42 |
+
# Create version data
|
| 43 |
+
version_data = {
|
| 44 |
+
"version": version_number,
|
| 45 |
+
"timestamp": timestamp,
|
| 46 |
+
"build": "development" if os.getenv('NODE_ENV') != 'production' else "production",
|
| 47 |
+
"commit": os.getenv('GIT_COMMIT', 'local'),
|
| 48 |
+
"assets": assets
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
# Write version file
|
| 52 |
+
with open(version_file, 'w') as f:
|
| 53 |
+
json.dump(version_data, f, indent=4)
|
| 54 |
+
|
| 55 |
+
print(f" Updated version to: {version_number}")
|
| 56 |
+
print(f" Updated {len(assets)} asset versions")
|
| 57 |
+
|
| 58 |
+
return version_data
|
| 59 |
+
|
| 60 |
+
def clear_browser_cache():
|
| 61 |
+
"""Provide instructions for clearing browser cache"""
|
| 62 |
+
print("\n To clear browser cache completely:")
|
| 63 |
+
print("1. Open Developer Tools (F12)")
|
| 64 |
+
print("2. Right-click the refresh button")
|
| 65 |
+
print("3. Select 'Empty Cache and Hard Reload'")
|
| 66 |
+
print("4. Or use Ctrl+Shift+R (Windows) / Cmd+Shift+R (Mac)")
|
| 67 |
+
print("\n For Service Workers:")
|
| 68 |
+
print("1. Go to Application tab in DevTools")
|
| 69 |
+
print("2. Click 'Service Workers' in sidebar")
|
| 70 |
+
print("3. Click 'Unregister' next to your service worker")
|
| 71 |
+
print("4. Refresh the page")
|
| 72 |
+
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
print(" TreeTrack Version Manager")
|
| 75 |
+
print("=" * 30)
|
| 76 |
+
|
| 77 |
+
try:
|
| 78 |
+
version_data = update_version()
|
| 79 |
+
clear_browser_cache()
|
| 80 |
+
|
| 81 |
+
print(f"\n Version update complete!")
|
| 82 |
+
print(f"Current version: {version_data['version']}")
|
| 83 |
+
|
| 84 |
+
except Exception as e:
|
| 85 |
+
print(f" Error updating version: {e}")
|
| 86 |
+
exit(1)
|
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "3.1754653904",
|
| 3 |
+
"timestamp": 1754653904,
|
| 4 |
+
"build": "hf_spaces",
|
| 5 |
+
"commit": "deployed",
|
| 6 |
+
"cache_cleared": true,
|
| 7 |
+
"deployment": "huggingface_spaces",
|
| 8 |
+
"port": 7860
|
| 9 |
+
}
|