Files
qr-code_manager/app/utils/url_shortener.py
ske087 53f5c513d4 🚀 Major improvements: Fix short URLs, separate public/admin views, and enhance UX
 Fixed Critical Issues:
- Fixed dynamic QR code short URL redirect functionality
- Resolved data consistency issues with multiple LinkPageManager instances
- Fixed worker concurrency problems in Gunicorn configuration

🎨 UI/UX Enhancements:
- Separated public page from admin statistics view
- Created clean public_page.html for QR code users (no admin info)
- Added comprehensive statistics_page.html for admin analytics
- Enhanced dashboard with separate 'Manage' and 'Stats' buttons
- Improved navigation flow throughout the application

🔧 Technical Improvements:
- Added URLShortener instance reloading for data consistency
- Reduced Gunicorn workers to 1 to prevent file conflicts
- Increased timeout to 60s for better performance
- Enhanced debug logging for troubleshooting
- Added proper error handling and 404 responses

📁 New Files:
- app/templates/public_page.html - Clean public interface
- app/templates/statistics_page.html - Admin analytics dashboard

�� Modified Files:
- app/routes/main.py - Added /stats route, improved short URL handling
- app/templates/edit_links.html - Added Statistics button
- app/templates/index.html - Added Stats button for QR codes
- app/utils/link_manager.py - Enhanced data reloading
- app/utils/url_shortener.py - Added debug logging
- gunicorn.conf.py - Optimized worker configuration

This update provides a professional separation between public content and admin functionality while ensuring reliable short URL operation.
2025-07-18 09:21:36 -04:00

132 lines
4.6 KiB
Python
Executable File

"""
URL Shortener utilities for QR Code Manager
"""
import os
import uuid
import string
import random
import json
from datetime import datetime
# Data storage directory
DATA_DIR = 'data'
SHORT_URLS_FILE = os.path.join(DATA_DIR, 'short_urls.json')
# Ensure data directory exists
os.makedirs(DATA_DIR, exist_ok=True)
class URLShortener:
def __init__(self):
self.base_domain = os.environ.get('APP_DOMAIN', 'localhost:5000')
# Ensure we have the protocol
if not self.base_domain.startswith(('http://', 'https://')):
# Use HTTPS for production domains, HTTP for localhost
protocol = 'https://' if 'localhost' not in self.base_domain else 'http://'
self.base_domain = f"{protocol}{self.base_domain}"
self.short_urls_db = self._load_short_urls()
def _load_short_urls(self):
"""Load short URLs from JSON file"""
try:
if os.path.exists(SHORT_URLS_FILE):
with open(SHORT_URLS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
except Exception as e:
print(f"Error loading short URLs: {e}")
return {}
def _save_short_urls(self):
"""Save short URLs to JSON file"""
try:
with open(SHORT_URLS_FILE, 'w', encoding='utf-8') as f:
json.dump(self.short_urls_db, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"Error saving short URLs: {e}")
def generate_short_code(self, length=6):
"""Generate a random short code"""
characters = string.ascii_letters + string.digits
while True:
short_code = ''.join(random.choice(characters) for _ in range(length))
# Ensure uniqueness
if short_code not in self.short_urls_db:
return short_code
def create_short_url(self, original_url, custom_code=None, title=""):
"""Create a shortened URL"""
print(f"DEBUG: URLShortener.create_short_url called with url='{original_url}', custom_code='{custom_code}', title='{title}'")
# Generate or use custom short code
if custom_code and custom_code not in self.short_urls_db:
short_code = custom_code
print(f"DEBUG: Using custom short code: {short_code}")
else:
short_code = self.generate_short_code()
print(f"DEBUG: Generated short code: {short_code}")
# Ensure original URL has protocol
if not original_url.startswith(('http://', 'https://')):
original_url = f'https://{original_url}'
# Create URL record
url_data = {
'id': str(uuid.uuid4()),
'short_code': short_code,
'original_url': original_url,
'title': title,
'clicks': 0,
'created_at': datetime.now().isoformat(),
'last_accessed': None
}
print(f"DEBUG: Adding to short_urls_db: {short_code} -> {url_data}")
self.short_urls_db[short_code] = url_data
print(f"DEBUG: Saving short URLs to file")
self._save_short_urls() # Persist to file
print(f"DEBUG: Short URLs saved successfully")
# Return the complete short URL
short_url = f"{self.base_domain}/s/{short_code}"
print(f"DEBUG: Returning short URL: {short_url}")
return {
'short_url': short_url,
'short_code': short_code,
'original_url': original_url,
'id': url_data['id']
}
def get_original_url(self, short_code):
"""Get original URL from short code and track click"""
if short_code in self.short_urls_db:
url_data = self.short_urls_db[short_code]
# Track click
url_data['clicks'] += 1
url_data['last_accessed'] = datetime.now().isoformat()
self._save_short_urls() # Persist to file
return url_data['original_url']
return None
def get_url_stats(self, short_code):
"""Get statistics for a short URL"""
return self.short_urls_db.get(short_code)
def list_urls(self):
"""List all short URLs"""
return list(self.short_urls_db.values())
def delete_url(self, short_code):
"""Delete a short URL"""
if short_code in self.short_urls_db:
del self.short_urls_db[short_code]
self._save_short_urls() # Persist to file
return True
return False
def url_exists(self, short_code):
"""Check if short URL exists"""
return short_code in self.short_urls_db