- Added environment variable loading with python-dotenv - Fixed Docker session permissions by using /tmp directory - Updated .dockerignore to include .env file properly - Enhanced docker-compose.yml with env_file directive - Fixed Gunicorn configuration for Docker compatibility - Updated README.md with comprehensive deployment docs - Cleaned up debug logging from API routes - Added DOMAIN_SETUP.md for reverse proxy guidance - All production issues resolved and tested working - Application now accessible at qr.moto-adv.com
123 lines
4.1 KiB
Python
Executable File
123 lines
4.1 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"""
|
|
# Generate or use custom short code
|
|
if custom_code and custom_code not in self.short_urls_db:
|
|
short_code = custom_code
|
|
else:
|
|
short_code = self.generate_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
|
|
}
|
|
|
|
self.short_urls_db[short_code] = url_data
|
|
self._save_short_urls() # Persist to file
|
|
|
|
# Return the complete short URL
|
|
short_url = f"{self.base_domain}/s/{short_code}"
|
|
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
|