Consolidate 3 separate applications (reports-app, data-entry-app, telegram-bot) into a unified
architecture with single backend and frontend:
Backend Changes:
- Unified FastAPI backend at backend/ with modular structure
- Modules: reports, data_entry, telegram in backend/modules/
- Centralized config.py and main.py with all routers registered
- Single worker mode (--workers 1) for Telegram bot compatibility
- Shared Oracle connection pool and JWT authentication
- Unified requirements.txt and environment configuration
Frontend Changes:
- Single Vue.js SPA with module-based routing
- Unified frontend at src/ with modules in src/modules/{reports,data-entry}/
- Shared components and stores in src/shared/
- Error boundaries for module isolation
- Dual API proxy in Vite for module communication
Infrastructure:
- New unified startup scripts: start-prod.sh, start-test.sh, start-backend.sh
- Environment templates: .env.dev.example, .env.test.example, .env.prod.example
- Updated deployment scripts for Windows IIS
- Simplified SSH tunnel management
Documentation:
- Comprehensive CLAUDE.md with architecture overview
- Module-specific docs in docs/{data-entry,telegram}/
- Architecture decision records in docs/ARCHITECTURE-DECISIONS.md
- Deployment guides consolidated in deployment/windows/docs/
This migration reduces complexity, improves maintainability, and enables easier
deployment while maintaining all existing functionality.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
151 lines
4.1 KiB
Python
151 lines
4.1 KiB
Python
"""
|
|
Cache key generation utilities
|
|
"""
|
|
import hashlib
|
|
import json
|
|
from typing import Any, List, Optional
|
|
|
|
|
|
def generate_cache_key(cache_type: str, key_params: Optional[List[str]], args: tuple, kwargs: dict) -> str:
|
|
"""
|
|
Generate cache key from function parameters
|
|
|
|
Format: "{cache_type}:{param1_value}:{param2_value}:..."
|
|
|
|
Args:
|
|
cache_type: Type of cache (e.g., 'dashboard_summary', 'invoices')
|
|
key_params: List of parameter names to include in key
|
|
args: Positional arguments from function call
|
|
kwargs: Keyword arguments from function call
|
|
|
|
Returns:
|
|
Cache key string
|
|
|
|
Examples:
|
|
generate_cache_key('schema', ['company_id'], (123,), {})
|
|
-> "schema:123"
|
|
|
|
generate_cache_key('dashboard_summary', ['company', 'username'], (), {'company': '123', 'username': 'john'})
|
|
-> "dashboard_summary:123:john"
|
|
|
|
generate_cache_key('invoices', ['company', 'invoice_type', 'status'], (123, 'CLIENTI', 'neplatite'), {})
|
|
-> "invoices:123:CLIENTI:neplatite"
|
|
"""
|
|
key_parts = [cache_type]
|
|
|
|
if not key_params:
|
|
# No specific params - use all args/kwargs (fallback)
|
|
if args:
|
|
key_parts.extend([str(arg) for arg in args])
|
|
if kwargs:
|
|
# Sort kwargs for consistent key generation
|
|
sorted_kwargs = sorted(kwargs.items())
|
|
key_parts.extend([f"{k}={v}" for k, v in sorted_kwargs])
|
|
else:
|
|
# Extract specific params
|
|
for i, param_name in enumerate(key_params):
|
|
# Try to get from kwargs first
|
|
if param_name in kwargs:
|
|
value = kwargs[param_name]
|
|
# Then try positional args
|
|
elif i < len(args):
|
|
value = args[i]
|
|
else:
|
|
# Parameter not found - use placeholder
|
|
value = "none"
|
|
|
|
key_parts.append(str(value))
|
|
|
|
return ":".join(key_parts)
|
|
|
|
|
|
def generate_key_pattern(cache_type: str, company_id: Optional[int] = None) -> str:
|
|
"""
|
|
Generate cache key pattern for matching multiple keys
|
|
|
|
Used for invalidation by type or company
|
|
|
|
Args:
|
|
cache_type: Type of cache
|
|
company_id: Optional company ID to filter by
|
|
|
|
Returns:
|
|
Pattern string (prefix)
|
|
|
|
Examples:
|
|
generate_key_pattern('dashboard_summary')
|
|
-> "dashboard_summary:"
|
|
|
|
generate_key_pattern('dashboard_summary', 123)
|
|
-> "dashboard_summary:123"
|
|
"""
|
|
if company_id is not None:
|
|
return f"{cache_type}:{company_id}"
|
|
return f"{cache_type}:"
|
|
|
|
|
|
def hash_complex_params(params: dict) -> str:
|
|
"""
|
|
Generate hash for complex parameters (e.g., filters, queries)
|
|
|
|
Used when cache key would be too long with full param values
|
|
|
|
Args:
|
|
params: Dictionary of parameters to hash
|
|
|
|
Returns:
|
|
8-character hash string
|
|
|
|
Example:
|
|
filters = {'status': 'neplatite', 'date_from': '2024-01-01', 'date_to': '2024-12-31'}
|
|
hash_complex_params(filters)
|
|
-> "a3f8b2c1"
|
|
"""
|
|
# Sort keys for consistent hashing
|
|
sorted_params = json.dumps(params, sort_keys=True)
|
|
hash_obj = hashlib.sha256(sorted_params.encode())
|
|
# Return first 8 characters of hex digest
|
|
return hash_obj.hexdigest()[:8]
|
|
|
|
|
|
def extract_company_id_from_key(cache_key: str) -> Optional[int]:
|
|
"""
|
|
Extract company_id from cache key
|
|
|
|
Assumes format: "cache_type:company_id:..."
|
|
|
|
Args:
|
|
cache_key: Cache key string
|
|
|
|
Returns:
|
|
Company ID or None if not found
|
|
|
|
Example:
|
|
extract_company_id_from_key("dashboard_summary:123:john")
|
|
-> 123
|
|
"""
|
|
parts = cache_key.split(":")
|
|
if len(parts) >= 2:
|
|
try:
|
|
return int(parts[1])
|
|
except (ValueError, TypeError):
|
|
pass
|
|
return None
|
|
|
|
|
|
def extract_cache_type_from_key(cache_key: str) -> str:
|
|
"""
|
|
Extract cache_type from cache key
|
|
|
|
Args:
|
|
cache_key: Cache key string
|
|
|
|
Returns:
|
|
Cache type (first part before colon)
|
|
|
|
Example:
|
|
extract_cache_type_from_key("dashboard_summary:123:john")
|
|
-> "dashboard_summary"
|
|
"""
|
|
return cache_key.split(":")[0]
|