feat: Migrate to ultrathin monolith architecture
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>
This commit is contained in:
150
backend/modules/reports/cache/keys.py
vendored
Normal file
150
backend/modules/reports/cache/keys.py
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
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]
|
||||
Reference in New Issue
Block a user