Spaces:
Sleeping
Sleeping
| """ | |
| Rooting Future - Public Plan Sharing Manager v1.0 | |
| Gestisce la creazione e validazione di link pubblici per condivisione piani strategici | |
| """ | |
| import uuid | |
| import sqlite3 | |
| from datetime import datetime, timedelta | |
| from typing import Optional, Dict, Any | |
| import hashlib | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| class ShareManager: | |
| """Gestisce condivisione pubblica dei piani strategici""" | |
| def __init__(self, db_path: str): | |
| self.db_path = db_path | |
| self._init_db() | |
| def _init_db(self): | |
| """Crea tabella public_shares se non esiste""" | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| CREATE TABLE IF NOT EXISTS public_shares ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| share_token TEXT UNIQUE NOT NULL, | |
| plan_id TEXT NOT NULL, | |
| created_by INTEGER NOT NULL, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| expires_at TIMESTAMP NOT NULL, | |
| is_active INTEGER DEFAULT 1, | |
| view_count INTEGER DEFAULT 0, | |
| last_viewed_at TIMESTAMP, | |
| password_hash TEXT, | |
| allow_download INTEGER DEFAULT 0, | |
| FOREIGN KEY(plan_id) REFERENCES strategic_plans(plan_id), | |
| FOREIGN KEY(created_by) REFERENCES users(id) | |
| ) | |
| """) | |
| # Index per performance | |
| cursor.execute(""" | |
| CREATE INDEX IF NOT EXISTS idx_share_token | |
| ON public_shares(share_token) | |
| """) | |
| cursor.execute(""" | |
| CREATE INDEX IF NOT EXISTS idx_plan_id_active | |
| ON public_shares(plan_id, is_active) | |
| """) | |
| conn.commit() | |
| conn.close() | |
| logger.info("[SHARE MANAGER] Database initialized") | |
| def create_share( | |
| self, | |
| plan_id: str, | |
| created_by: int, | |
| expires_days: int = 30, | |
| password: Optional[str] = None, | |
| allow_download: bool = False | |
| ) -> str: | |
| """ | |
| Crea un link di condivisione pubblico. | |
| Args: | |
| plan_id: ID del piano da condividere | |
| created_by: User ID del creatore | |
| expires_days: Giorni prima della scadenza (default 30) | |
| password: Password opzionale per proteggere l'accesso | |
| allow_download: Permetti download PDF/DOCX (default False) | |
| Returns: | |
| share_token: Token univoco per l'accesso pubblico | |
| """ | |
| share_token = str(uuid.uuid4()) | |
| expires_at = datetime.now() + timedelta(days=expires_days) | |
| password_hash = None | |
| if password: | |
| password_hash = hashlib.sha256(password.encode()).hexdigest() | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| INSERT INTO public_shares | |
| (share_token, plan_id, created_by, expires_at, password_hash, allow_download) | |
| VALUES (?, ?, ?, ?, ?, ?) | |
| """, (share_token, plan_id, created_by, expires_at, password_hash, int(allow_download))) | |
| conn.commit() | |
| conn.close() | |
| logger.info(f"[SHARE MANAGER] Created share token for plan {plan_id}, expires {expires_at}") | |
| return share_token | |
| def validate_share(self, share_token: str, password: Optional[str] = None) -> Optional[Dict[str, Any]]: | |
| """ | |
| Valida un token di condivisione. | |
| Returns: | |
| Dict con info share se valido, None se invalido/scaduto | |
| """ | |
| conn = sqlite3.connect(self.db_path) | |
| conn.row_factory = sqlite3.Row | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT * FROM public_shares | |
| WHERE share_token = ? AND is_active = 1 | |
| """, (share_token,)) | |
| row = cursor.fetchone() | |
| if not row: | |
| conn.close() | |
| logger.warning(f"[SHARE MANAGER] Invalid token: {share_token}") | |
| return None | |
| share = dict(row) | |
| # Check scadenza | |
| expires_at = datetime.fromisoformat(share['expires_at']) | |
| if datetime.now() > expires_at: | |
| conn.close() | |
| logger.warning(f"[SHARE MANAGER] Expired token: {share_token}") | |
| return None | |
| # Check password se presente | |
| if share['password_hash']: | |
| if not password: | |
| # Password richiesta ma non fornita | |
| share['requires_password'] = True | |
| conn.close() | |
| return share | |
| password_hash = hashlib.sha256(password.encode()).hexdigest() | |
| if password_hash != share['password_hash']: | |
| conn.close() | |
| logger.warning(f"[SHARE MANAGER] Wrong password for token: {share_token}") | |
| return None | |
| # Incrementa view count | |
| cursor.execute(""" | |
| UPDATE public_shares | |
| SET view_count = view_count + 1, | |
| last_viewed_at = CURRENT_TIMESTAMP | |
| WHERE share_token = ? | |
| """, (share_token,)) | |
| conn.commit() | |
| conn.close() | |
| logger.info(f"[SHARE MANAGER] Validated token {share_token}, views: {share['view_count'] + 1}") | |
| return share | |
| def revoke_share(self, share_token: str, user_id: int) -> bool: | |
| """Revoca un link di condivisione""" | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| UPDATE public_shares | |
| SET is_active = 0 | |
| WHERE share_token = ? AND created_by = ? | |
| """, (share_token, user_id)) | |
| revoked = cursor.rowcount > 0 | |
| conn.commit() | |
| conn.close() | |
| if revoked: | |
| logger.info(f"[SHARE MANAGER] Revoked share token: {share_token}") | |
| return revoked | |
| def get_plan_shares(self, plan_id: str, user_id: int) -> list: | |
| """Ottiene tutti i link attivi per un piano""" | |
| conn = sqlite3.connect(self.db_path) | |
| conn.row_factory = sqlite3.Row | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| SELECT * FROM public_shares | |
| WHERE plan_id = ? AND created_by = ? AND is_active = 1 | |
| ORDER BY created_at DESC | |
| """, (plan_id, user_id)) | |
| rows = cursor.fetchall() | |
| conn.close() | |
| return [dict(row) for row in rows] | |
| def cleanup_expired(self) -> int: | |
| """Rimuove share scaduti (cron job)""" | |
| conn = sqlite3.connect(self.db_path) | |
| cursor = conn.cursor() | |
| cursor.execute(""" | |
| UPDATE public_shares | |
| SET is_active = 0 | |
| WHERE is_active = 1 AND expires_at < CURRENT_TIMESTAMP | |
| """) | |
| cleaned = cursor.rowcount | |
| conn.commit() | |
| conn.close() | |
| if cleaned > 0: | |
| logger.info(f"[SHARE MANAGER] Cleaned {cleaned} expired shares") | |
| return cleaned | |