From e938baa78ecbcf4ff3822295125a4697d89fec85 Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:07:58 +0700 Subject: [PATCH 1/5] - Add Settings menu for Stripe configuration- In the Member Profile page, Superadmin can assign new Role to the member- Stripe Configuration is now stored with encryption in Database --- .env.example | 13 +- .../4fa11836f7fd_add_role_audit_fields.py | 48 +++ .../ec4cb4a49cde_add_system_settings_table.py | 68 ++++ encryption_service.py | 122 ++++++ models.py | 38 ++ payment_service.py | 64 ++- server.py | 375 +++++++++++++++++- 7 files changed, 717 insertions(+), 11 deletions(-) create mode 100644 alembic/versions/4fa11836f7fd_add_role_audit_fields.py create mode 100644 alembic/versions/ec4cb4a49cde_add_system_settings_table.py create mode 100644 encryption_service.py diff --git a/.env.example b/.env.example index a20ff27..f2f1d6d 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,10 @@ JWT_SECRET=your-secret-key-change-this-in-production JWT_ALGORITHM=HS256 ACCESS_TOKEN_EXPIRE_MINUTES=30 +# Settings Encryption (for database-stored sensitive settings) +# Generate with: python -c "import secrets; print(secrets.token_urlsafe(64))" +SETTINGS_ENCRYPTION_KEY=your-encryption-key-generate-with-command-above + # SMTP Email Configuration (Port 465 - SSL/TLS) SMTP_HOST=p.konceptkit.com SMTP_PORT=465 @@ -28,7 +32,14 @@ SMTP_FROM_NAME=LOAF Membership # Frontend URL FRONTEND_URL=http://localhost:3000 -# Stripe Configuration (for future payment integration) +# Backend URL (for webhook URLs and API references) +# Used to construct Stripe webhook URL shown in Admin Settings +BACKEND_URL=http://localhost:8000 + +# Stripe Configuration (NOW DATABASE-DRIVEN via Admin Settings page) +# Configure Stripe credentials through the Admin Settings UI (requires SETTINGS_ENCRYPTION_KEY) +# No longer requires .env variables - managed through database for dynamic updates +# Legacy .env variables below are deprecated: # STRIPE_SECRET_KEY=sk_test_... # STRIPE_WEBHOOK_SECRET=whsec_... diff --git a/alembic/versions/4fa11836f7fd_add_role_audit_fields.py b/alembic/versions/4fa11836f7fd_add_role_audit_fields.py new file mode 100644 index 0000000..ccda8d9 --- /dev/null +++ b/alembic/versions/4fa11836f7fd_add_role_audit_fields.py @@ -0,0 +1,48 @@ +"""add_role_audit_fields + +Revision ID: 4fa11836f7fd +Revises: 013_sync_permissions +Create Date: 2026-01-16 17:21:40.514605 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision: str = '4fa11836f7fd' +down_revision: Union[str, None] = '013_sync_permissions' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Add role audit trail columns + op.add_column('users', sa.Column('role_changed_at', sa.DateTime(timezone=True), nullable=True)) + op.add_column('users', sa.Column('role_changed_by', UUID(as_uuid=True), nullable=True)) + + # Create foreign key constraint to track who changed the role + op.create_foreign_key( + 'fk_users_role_changed_by', + 'users', 'users', + ['role_changed_by'], ['id'], + ondelete='SET NULL' + ) + + # Create index for efficient querying by role change date + op.create_index('idx_users_role_changed_at', 'users', ['role_changed_at']) + + +def downgrade() -> None: + # Drop index first + op.drop_index('idx_users_role_changed_at') + + # Drop foreign key constraint + op.drop_constraint('fk_users_role_changed_by', 'users', type_='foreignkey') + + # Drop columns + op.drop_column('users', 'role_changed_by') + op.drop_column('users', 'role_changed_at') diff --git a/alembic/versions/ec4cb4a49cde_add_system_settings_table.py b/alembic/versions/ec4cb4a49cde_add_system_settings_table.py new file mode 100644 index 0000000..d403c1c --- /dev/null +++ b/alembic/versions/ec4cb4a49cde_add_system_settings_table.py @@ -0,0 +1,68 @@ +"""add_system_settings_table + +Revision ID: ec4cb4a49cde +Revises: 4fa11836f7fd +Create Date: 2026-01-16 18:16:00.283455 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision: str = 'ec4cb4a49cde' +down_revision: Union[str, None] = '4fa11836f7fd' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Create enum for setting types (only if not exists) + op.execute(""" + DO $$ BEGIN + CREATE TYPE settingtype AS ENUM ('plaintext', 'encrypted', 'json'); + EXCEPTION + WHEN duplicate_object THEN null; + END $$; + """) + + # Create system_settings table + op.execute(""" + CREATE TABLE system_settings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + setting_key VARCHAR(100) UNIQUE NOT NULL, + setting_value TEXT, + setting_type settingtype NOT NULL DEFAULT 'plaintext'::settingtype, + description TEXT, + updated_by UUID REFERENCES users(id) ON DELETE SET NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + is_sensitive BOOLEAN NOT NULL DEFAULT FALSE + ); + + COMMENT ON COLUMN system_settings.setting_key IS 'Unique setting identifier (e.g., stripe_secret_key)'; + COMMENT ON COLUMN system_settings.setting_value IS 'Setting value (encrypted if setting_type is encrypted)'; + COMMENT ON COLUMN system_settings.setting_type IS 'Type of setting: plaintext, encrypted, or json'; + COMMENT ON COLUMN system_settings.description IS 'Human-readable description of the setting'; + COMMENT ON COLUMN system_settings.updated_by IS 'User who last updated this setting'; + COMMENT ON COLUMN system_settings.is_sensitive IS 'Whether this setting contains sensitive data'; + """) + + # Create indexes + op.create_index('idx_system_settings_key', 'system_settings', ['setting_key']) + op.create_index('idx_system_settings_updated_at', 'system_settings', ['updated_at']) + + +def downgrade() -> None: + # Drop indexes + op.drop_index('idx_system_settings_updated_at') + op.drop_index('idx_system_settings_key') + + # Drop table + op.drop_table('system_settings') + + # Drop enum + op.execute('DROP TYPE IF EXISTS settingtype') diff --git a/encryption_service.py b/encryption_service.py new file mode 100644 index 0000000..f12ee46 --- /dev/null +++ b/encryption_service.py @@ -0,0 +1,122 @@ +""" +Encryption service for sensitive settings stored in database. + +Uses Fernet symmetric encryption (AES-128 in CBC mode with HMAC authentication). +The encryption key is derived from a master secret stored in .env. +""" + +import os +import base64 +from cryptography.fernet import Fernet +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.backends import default_backend + + +class EncryptionService: + """Service for encrypting and decrypting sensitive configuration values""" + + def __init__(self): + # Get master encryption key from environment + # This should be a long, random string (e.g., 64 characters) + # Generate one with: python -c "import secrets; print(secrets.token_urlsafe(64))" + self.master_secret = os.environ.get('SETTINGS_ENCRYPTION_KEY') + + if not self.master_secret: + raise ValueError( + "SETTINGS_ENCRYPTION_KEY environment variable not set. " + "Generate one with: python -c \"import secrets; print(secrets.token_urlsafe(64))\"" + ) + + # Derive encryption key from master secret using PBKDF2HMAC + # This adds an extra layer of security + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=b'systemsettings', # Fixed salt (OK for key derivation from strong secret) + iterations=100000, + backend=default_backend() + ) + key = base64.urlsafe_b64encode(kdf.derive(self.master_secret.encode())) + self.cipher = Fernet(key) + + def encrypt(self, plaintext: str) -> str: + """ + Encrypt a plaintext string. + + Args: + plaintext: The string to encrypt + + Returns: + Base64-encoded encrypted string + """ + if not plaintext: + return "" + + encrypted_bytes = self.cipher.encrypt(plaintext.encode()) + return encrypted_bytes.decode('utf-8') + + def decrypt(self, encrypted: str) -> str: + """ + Decrypt an encrypted string. + + Args: + encrypted: The base64-encoded encrypted string + + Returns: + Decrypted plaintext string + + Raises: + cryptography.fernet.InvalidToken: If decryption fails (wrong key or corrupted data) + """ + if not encrypted: + return "" + + decrypted_bytes = self.cipher.decrypt(encrypted.encode()) + return decrypted_bytes.decode('utf-8') + + def is_encrypted(self, value: str) -> bool: + """ + Check if a value appears to be encrypted (starts with Fernet token format). + + This is a heuristic check - not 100% reliable but useful for validation. + + Args: + value: String to check + + Returns: + True if value looks like a Fernet token + """ + if not value: + return False + + # Fernet tokens are base64-encoded and start with version byte (gAAAAA...) + # They're always > 60 characters + try: + return len(value) > 60 and value.startswith('gAAAAA') + except: + return False + + +# Global encryption service instance +# Initialize on module import so it fails fast if encryption key is missing +try: + encryption_service = EncryptionService() +except ValueError as e: + print(f"WARNING: {e}") + print("Encryption service will not be available.") + encryption_service = None + + +def get_encryption_service() -> EncryptionService: + """ + Get the global encryption service instance. + + Raises: + ValueError: If encryption service is not initialized (missing SETTINGS_ENCRYPTION_KEY) + """ + if encryption_service is None: + raise ValueError( + "Encryption service not initialized. Set SETTINGS_ENCRYPTION_KEY environment variable." + ) + return encryption_service diff --git a/models.py b/models.py index a8d8d30..c0b50e0 100644 --- a/models.py +++ b/models.py @@ -137,6 +137,10 @@ class User(Base): wordpress_user_id = Column(BigInteger, nullable=True, comment="Original WordPress user ID") wordpress_registered_date = Column(DateTime(timezone=True), nullable=True, comment="Original WordPress registration date") + # Role Change Audit Trail + role_changed_at = Column(DateTime(timezone=True), nullable=True, comment="Timestamp when role was last changed") + role_changed_by = Column(UUID(as_uuid=True), ForeignKey('users.id', ondelete='SET NULL'), nullable=True, comment="Admin who changed the role") + created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) @@ -145,6 +149,7 @@ class User(Base): events_created = relationship("Event", back_populates="creator") rsvps = relationship("EventRSVP", back_populates="user") subscriptions = relationship("Subscription", back_populates="user", foreign_keys="Subscription.user_id") + role_changer = relationship("User", foreign_keys=[role_changed_by], remote_side="User.id", post_update=True) class Event(Base): __tablename__ = "events" @@ -509,3 +514,36 @@ class ImportRollbackAudit(Base): # Relationships import_job = relationship("ImportJob") admin_user = relationship("User", foreign_keys=[rolled_back_by]) + + +# ============================================================ +# System Settings Models +# ============================================================ + +class SettingType(enum.Enum): + plaintext = "plaintext" + encrypted = "encrypted" + json = "json" + + +class SystemSettings(Base): + """System-wide configuration settings stored in database""" + __tablename__ = "system_settings" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + setting_key = Column(String(100), unique=True, nullable=False, index=True) + setting_value = Column(Text, nullable=True) + setting_type = Column(SQLEnum(SettingType), default=SettingType.plaintext, nullable=False) + description = Column(Text, nullable=True) + updated_by = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True) + created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False) + updated_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False) + is_sensitive = Column(Boolean, default=False, nullable=False) + + # Relationships + updater = relationship("User", foreign_keys=[updated_by]) + + # Index on updated_at for audit queries + __table_args__ = ( + Index('idx_system_settings_updated_at', 'updated_at'), + ) diff --git a/payment_service.py b/payment_service.py index 562ddd2..ea95351 100644 --- a/payment_service.py +++ b/payment_service.py @@ -11,11 +11,9 @@ from datetime import datetime, timezone, timedelta # Load environment variables load_dotenv() -# Initialize Stripe with secret key -stripe.api_key = os.getenv("STRIPE_SECRET_KEY") - -# Stripe webhook secret for signature verification -STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET") +# NOTE: Stripe credentials are now database-driven +# These .env fallbacks are kept for backward compatibility only +# The actual credentials are loaded dynamically from system_settings table def create_checkout_session( user_id: str, @@ -23,11 +21,15 @@ def create_checkout_session( plan_id: str, stripe_price_id: str, success_url: str, - cancel_url: str + cancel_url: str, + db = None ): """ Create a Stripe Checkout session for subscription payment. + Args: + db: Database session (optional, for reading Stripe credentials from database) + Args: user_id: User's UUID user_email: User's email address @@ -39,6 +41,28 @@ def create_checkout_session( Returns: dict: Checkout session object with session ID and URL """ + # Load Stripe API key from database if available + if db: + try: + # Import here to avoid circular dependency + from models import SystemSettings, SettingType + from encryption_service import get_encryption_service + + setting = db.query(SystemSettings).filter( + SystemSettings.setting_key == 'stripe_secret_key' + ).first() + + if setting and setting.setting_value: + encryption_service = get_encryption_service() + stripe.api_key = encryption_service.decrypt(setting.setting_value) + except Exception as e: + # Fallback to .env if database read fails + print(f"Failed to read Stripe key from database: {e}") + stripe.api_key = os.getenv("STRIPE_SECRET_KEY") + else: + # Fallback to .env if no db session + stripe.api_key = os.getenv("STRIPE_SECRET_KEY") + try: # Create Checkout Session checkout_session = stripe.checkout.Session.create( @@ -74,13 +98,14 @@ def create_checkout_session( raise Exception(f"Stripe error: {str(e)}") -def verify_webhook_signature(payload: bytes, sig_header: str) -> dict: +def verify_webhook_signature(payload: bytes, sig_header: str, db=None) -> dict: """ Verify Stripe webhook signature and construct event. Args: payload: Raw webhook payload bytes sig_header: Stripe signature header + db: Database session (optional, for reading webhook secret from database) Returns: dict: Verified webhook event @@ -88,9 +113,32 @@ def verify_webhook_signature(payload: bytes, sig_header: str) -> dict: Raises: ValueError: If signature verification fails """ + # Load webhook secret from database if available + webhook_secret = None + if db: + try: + from models import SystemSettings + from encryption_service import get_encryption_service + + setting = db.query(SystemSettings).filter( + SystemSettings.setting_key == 'stripe_webhook_secret' + ).first() + + if setting and setting.setting_value: + encryption_service = get_encryption_service() + webhook_secret = encryption_service.decrypt(setting.setting_value) + except Exception as e: + print(f"Failed to read webhook secret from database: {e}") + webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET") + else: + webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET") + + if not webhook_secret: + raise ValueError("STRIPE_WEBHOOK_SECRET not configured") + try: event = stripe.Webhook.construct_event( - payload, sig_header, STRIPE_WEBHOOK_SECRET + payload, sig_header, webhook_secret ) return event except ValueError as e: diff --git a/server.py b/server.py index 298f7fc..cbbbc48 100644 --- a/server.py +++ b/server.py @@ -514,6 +514,10 @@ class AcceptInvitationRequest(BaseModel): zipcode: Optional[str] = None date_of_birth: Optional[datetime] = None +class ChangeRoleRequest(BaseModel): + role: str + role_id: Optional[str] = None # For custom roles + # Auth Routes @api_router.post("/auth/register") async def register(request: RegisterRequest, db: Session = Depends(get_db)): @@ -2527,6 +2531,102 @@ async def admin_reset_user_password( return {"message": f"Password reset for {user.email}. Temporary password emailed."} +@api_router.put("/admin/users/{user_id}/role") +async def change_user_role( + user_id: str, + request: ChangeRoleRequest, + current_user: User = Depends(require_permission("users.edit")), + db: Session = Depends(get_db) +): + """ + Change an existing user's role with privilege escalation prevention. + + Requires: users.edit permission + + Rules: + - Superadmin: Can assign any role (including superadmin) + - Admin: Can assign admin, finance, member, guest, and non-elevated custom roles + - Admin CANNOT assign: superadmin or custom roles with elevated permissions + - Users CANNOT change their own role + """ + + # 1. Fetch target user + target_user = db.query(User).filter(User.id == user_id).first() + if not target_user: + raise HTTPException(status_code=404, detail="User not found") + + # 2. Prevent self-role-change + if str(target_user.id) == str(current_user.id): + raise HTTPException( + status_code=403, + detail="You cannot change your own role" + ) + + # 3. Validate new role + if request.role not in ['guest', 'member', 'admin', 'finance', 'superadmin']: + raise HTTPException(status_code=400, detail="Invalid role") + + # 4. Privilege escalation check + if current_user.role != 'superadmin': + # Non-superadmin cannot assign superadmin role + if request.role == 'superadmin': + raise HTTPException( + status_code=403, + detail="Only superadmin can assign superadmin role" + ) + + # Check custom role elevation + if request.role_id: + custom_role = db.query(Role).filter(Role.id == request.role_id).first() + if not custom_role: + raise HTTPException(status_code=404, detail="Custom role not found") + + # Check if custom role has elevated permissions + elevated_permissions = ['users.delete', 'roles.create', 'roles.edit', + 'roles.delete', 'permissions.edit'] + role_perms = db.query(Permission.name).join(RolePermission).filter( + RolePermission.role_id == custom_role.id, + Permission.name.in_(elevated_permissions) + ).all() + + if role_perms: + raise HTTPException( + status_code=403, + detail=f"Cannot assign role with elevated permissions: {custom_role.name}" + ) + + # 5. Update role with audit trail + old_role = target_user.role + old_role_id = target_user.role_id + + target_user.role = request.role + target_user.role_id = request.role_id if request.role_id else None + target_user.role_changed_at = datetime.now(timezone.utc) + target_user.role_changed_by = current_user.id + target_user.updated_at = datetime.now(timezone.utc) + + db.commit() + db.refresh(target_user) + + # Log admin action + logger.info( + f"Admin {current_user.email} changed role for user {target_user.email} " + f"from {old_role} to {request.role}" + ) + + return { + "message": f"Role changed from {old_role} to {request.role}", + "user": { + "id": str(target_user.id), + "email": target_user.email, + "name": f"{target_user.first_name} {target_user.last_name}", + "old_role": old_role, + "new_role": target_user.role, + "changed_by": f"{current_user.first_name} {current_user.last_name}", + "changed_at": target_user.role_changed_at.isoformat() + } + } + @api_router.post("/admin/users/{user_id}/resend-verification") async def admin_resend_verification( user_id: str, @@ -6197,8 +6297,8 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)): raise HTTPException(status_code=400, detail="Missing stripe-signature header") try: - # Verify webhook signature - event = verify_webhook_signature(payload, sig_header) + # Verify webhook signature (pass db for reading webhook secret from database) + event = verify_webhook_signature(payload, sig_header, db) except ValueError as e: logger.error(f"Webhook signature verification failed: {str(e)}") raise HTTPException(status_code=400, detail=str(e)) @@ -6298,6 +6398,277 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)): return {"status": "success"} +# ============================================================================ +# ADMIN SETTINGS ENDPOINTS +# ============================================================================ + +# Helper functions for system settings +def get_setting(db: Session, key: str, decrypt: bool = False) -> str | None: + """ + Get a system setting value from database. + + Args: + db: Database session + key: Setting key to retrieve + decrypt: If True and setting_type is 'encrypted', decrypt the value + + Returns: + Setting value or None if not found + """ + from models import SystemSettings, SettingType + from encryption_service import get_encryption_service + + setting = db.query(SystemSettings).filter(SystemSettings.setting_key == key).first() + if not setting: + return None + + value = setting.setting_value + if decrypt and setting.setting_type == SettingType.encrypted and value: + try: + encryption_service = get_encryption_service() + value = encryption_service.decrypt(value) + except Exception as e: + print(f"Failed to decrypt setting {key}: {e}") + return None + + return value + + +def set_setting( + db: Session, + key: str, + value: str, + user_id: str, + setting_type: str = "plaintext", + description: str = None, + is_sensitive: bool = False, + encrypt: bool = False +) -> None: + """ + Set a system setting value in database. + + Args: + db: Database session + key: Setting key + value: Setting value + user_id: ID of user making the change + setting_type: Type of setting (plaintext, encrypted, json) + description: Human-readable description + is_sensitive: Whether this is sensitive data + encrypt: If True, encrypt the value before storing + """ + from models import SystemSettings, SettingType + from encryption_service import get_encryption_service + + # Encrypt value if requested + if encrypt and value: + encryption_service = get_encryption_service() + value = encryption_service.encrypt(value) + setting_type = "encrypted" + + # Find or create setting + setting = db.query(SystemSettings).filter(SystemSettings.setting_key == key).first() + + if setting: + # Update existing + setting.setting_value = value + setting.setting_type = SettingType[setting_type] + setting.updated_by = user_id + setting.updated_at = datetime.now(timezone.utc) + if description: + setting.description = description + setting.is_sensitive = is_sensitive + else: + # Create new + setting = SystemSettings( + setting_key=key, + setting_value=value, + setting_type=SettingType[setting_type], + description=description, + updated_by=user_id, + is_sensitive=is_sensitive + ) + db.add(setting) + + db.commit() + +@api_router.get("/admin/settings/stripe/status") +async def get_stripe_status( + current_user: User = Depends(get_current_superadmin), + db: Session = Depends(get_db) +): + """ + Get Stripe integration status (superadmin only). + + Returns: + - configured: Whether credentials exist in database + - secret_key_prefix: First 10 chars of secret key (for verification) + - webhook_configured: Whether webhook secret exists + - environment: test or live (based on key prefix) + - webhook_url: Full webhook URL for Stripe configuration + """ + import os + + # Read from database + secret_key = get_setting(db, 'stripe_secret_key', decrypt=True) + webhook_secret = get_setting(db, 'stripe_webhook_secret', decrypt=True) + + configured = bool(secret_key) + environment = 'unknown' + + if secret_key: + if secret_key.startswith('sk_test_'): + environment = 'test' + elif secret_key.startswith('sk_live_'): + environment = 'live' + + # Get backend URL from environment for webhook URL + # Try multiple environment variable patterns for flexibility + backend_url = ( + os.environ.get('BACKEND_URL') or + os.environ.get('API_URL') or + f"http://{os.environ.get('HOST', 'localhost')}:{os.environ.get('PORT', '8000')}" + ) + webhook_url = f"{backend_url}/api/webhooks/stripe" + + return { + "configured": configured, + "secret_key_prefix": secret_key[:10] if secret_key else None, + "secret_key_set": bool(secret_key), + "webhook_secret_set": bool(webhook_secret), + "environment": environment, + "webhook_url": webhook_url, + "instructions": { + "location": "Database (system_settings table)", + "required_settings": [ + "stripe_secret_key (sk_test_... or sk_live_...)", + "stripe_webhook_secret (whsec_...)" + ], + "restart_required": "No - changes take effect immediately" + } + } + +@api_router.post("/admin/settings/stripe/test-connection") +async def test_stripe_connection( + current_user: User = Depends(get_current_superadmin), + db: Session = Depends(get_db) +): + """ + Test Stripe API connection (superadmin only). + + Performs a simple API call to verify credentials work. + """ + import stripe + + # Read from database + secret_key = get_setting(db, 'stripe_secret_key', decrypt=True) + + if not secret_key: + raise HTTPException( + status_code=400, + detail="STRIPE_SECRET_KEY not configured in database. Please configure Stripe settings first." + ) + + try: + stripe.api_key = secret_key + + # Make a simple API call to test connection + balance = stripe.Balance.retrieve() + + return { + "success": True, + "message": "Stripe connection successful", + "environment": "test" if secret_key.startswith('sk_test_') else "live", + "balance": { + "available": balance.available, + "pending": balance.pending + } + } + except stripe.error.AuthenticationError as e: + raise HTTPException( + status_code=401, + detail=f"Stripe authentication failed: {str(e)}" + ) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Stripe connection test failed: {str(e)}" + ) + + +class UpdateStripeSettingsRequest(BaseModel): + """Request model for updating Stripe settings""" + secret_key: str = Field(..., min_length=1, description="Stripe secret key (sk_test_... or sk_live_...)") + webhook_secret: str = Field(..., min_length=1, description="Stripe webhook secret (whsec_...)") + + +@api_router.put("/admin/settings/stripe") +async def update_stripe_settings( + request: UpdateStripeSettingsRequest, + current_user: User = Depends(get_current_superadmin), + db: Session = Depends(get_db) +): + """ + Update Stripe integration settings (superadmin only). + + Stores Stripe credentials encrypted in the database. + Changes take effect immediately without server restart. + """ + # Validate secret key format + if not (request.secret_key.startswith('sk_test_') or request.secret_key.startswith('sk_live_')): + raise HTTPException( + status_code=400, + detail="Invalid Stripe secret key format. Must start with 'sk_test_' or 'sk_live_'" + ) + + # Validate webhook secret format + if not request.webhook_secret.startswith('whsec_'): + raise HTTPException( + status_code=400, + detail="Invalid Stripe webhook secret format. Must start with 'whsec_'" + ) + + try: + # Store secret key (encrypted) + set_setting( + db=db, + key='stripe_secret_key', + value=request.secret_key, + user_id=str(current_user.id), + description='Stripe API secret key for payment processing', + is_sensitive=True, + encrypt=True + ) + + # Store webhook secret (encrypted) + set_setting( + db=db, + key='stripe_webhook_secret', + value=request.webhook_secret, + user_id=str(current_user.id), + description='Stripe webhook secret for verifying webhook signatures', + is_sensitive=True, + encrypt=True + ) + + # Determine environment + environment = 'test' if request.secret_key.startswith('sk_test_') else 'live' + + return { + "success": True, + "message": "Stripe settings updated successfully", + "environment": environment, + "updated_at": datetime.now(timezone.utc).isoformat(), + "updated_by": f"{current_user.first_name} {current_user.last_name}" + } + + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Failed to update Stripe settings: {str(e)}" + ) + + # Include the router in the main app app.include_router(api_router) From d3a0cabede33a5af0c868fb30fc4cfcd39439778 Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Tue, 20 Jan 2026 23:51:38 +0700 Subject: [PATCH 2/5] - Details Column - Expandable chevron button for each row- Expandable Transaction Details - Click chevron to show/hide details- Payment Information Section:- Stripe Transaction IDs Section- Copy to Clipboard - One-click copy for all transaction IDs- Update Stripe webhook event permission on Stripe Config page. --- __pycache__/database.cpython-312.pyc | Bin 1158 -> 1425 bytes __pycache__/models.cpython-312.pyc | Bin 30190 -> 33150 bytes __pycache__/payment_service.cpython-312.pyc | Bin 10398 -> 12308 bytes __pycache__/server.cpython-312.pyc | Bin 269694 -> 295856 bytes ...1628264_add_stripe_transaction_metadata.py | 76 ++++++++++ models.py | 19 ++- server.py | 143 ++++++++++++++++-- 7 files changed, 221 insertions(+), 17 deletions(-) create mode 100644 alembic/versions/956ea1628264_add_stripe_transaction_metadata.py diff --git a/__pycache__/database.cpython-312.pyc b/__pycache__/database.cpython-312.pyc index 0234c7388e338a780cf729426cdfcfc017ec922d..86802418944657304dfaa2941dd90a1ed86c9962 100644 GIT binary patch delta 710 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 delta 4540 zcma)93v8QL754q(IE$UP<2;NG^*p7W=U<2;C! zeE$D?&b{}X^PO|<_5HKL-#=jq@5RS!Rs18^EzUXn^9dPj?|kXLDD`x-xi?t%4bs?lu|nJtnjNFE3{5|5t_-Fp_eDWf@hFrU zn%T49G@Q{K;`_=^!p}7N%s&2jGQQU<$vYaxZLU$#*7O`o`{B(mj)f_vuBQk;NOLnQ z6sFfib(@ES7t=eUVJW*4x^r^b;h-x=r-u2`d{{8;6wY)7^9o*L%m%+I{8HGuMp`$a zWlD1ULKeKVo(1b_es?|1n)TKsO$(|QA`BulBD{uRM-buTViWf#rewSE2o4}Wast9p z1Si5V=q@>92;k!@2#E-z2v_0HC52iWJ|cV-l1uLjHWl3N$_QqaNsNhb#{A7N-&r5b zl&5Rnz!{P8UY1?oSXNB32#;v@t2l`~YE*q?%CMO)cgnNbDTuCUi1dgSmoP^YoC=N)=0H<>23)JS5REeX^T~$bq3w2IwST6nT+IR(uje1ie!HmhE7zN_%)?o^WL~sk?11Nqz|5Z^DaJ_mU#Wn@2CTAD|rJX7JtW6efH?#0eb& zujo!V-BJmc%PQeJJKrfbiW*T9hz(U~7Oy_Flr0K@m{67Gz#nz#aH=d$kE57J{A0-_ zO+9?}XsKT>>Ua-`3(YVoGBKWahXgK349zOwl0q?wcLy!+4#}bMA{tK-SwJ6JrFc5k zY>+WZ@;rwg`3v~ijL^-YRc_~a(%_wh{M|WFlxAN|m%qlS`xh$rBNW2l3d4H<&q=PJ zSt5mq{-DSXlPrQWs{ob@-UK2PW!oL7?Hw%mKGzGlf*7cm%FD{vPIo%oOr(w`?La3b}b$Q6R~`M8M_ocutu zh6Yl^5U=!}DjFUr(0iJg{@~rAwiHZfm@`4Yr-pTbDa{eaYX-H$u@q|@e-pOj0 zC7VwYWh}UEbK%x02mju5GZFnLZo3QNK0*ybEdp*QFQ>&%_ck^!W_W$Fb3(FC*rr|l zFtIw_KKc^nPf-=?e=*wa@kw4;g98y=K!VnNWr_Dtg77*2_g2BozPXfzyoO(^vp+Os zeXaHTJqw2F)7^0SF~4w6>Fm`lwT#_>@z#9yE#I1;3Q1 z(Q=jHmirNG2=5@+;b2=<(H2VLmvE`AS4XbUL3ADNw&iGV;2H=yklubxxS?43r}n?F zFza~5UV1!FQ$vQVt!9+fZY)b9*_uk{$IXt-&Bm52{}^GpBVX&R&r8IOh|IT85<$%< zHRu%7LD&xFuGd*g@T0CzBK&#wK#rz;1DjE{e`8q^$-?{r+rcc`=B~tYTNl;Sl1-+A zM)4{W_b?(vIJI8VDV}AcHbN78esGRu!$)12!JCIfrq1PqVC`_NrY+Eut+C*oez?_> z|9{d%i8rM57SyenhL$V#MWYW|r`Kwqlx1Eokt`_J8zP_xw?ycMXL{#ZQLwRZPdEYD zO1eMk^T@U%Qm>aoDBUHv)?c;RMdUyI2a?Kh0|YDo_qOm$M!gAU2P#-OMda;)-7I!a zkXzCCwumN&_a#e)#)nJv!#6D3G(SYOAHjQ;yw=TWR4xg2k0{9>;v6L}F)kXHN1Wtk zt564(gWI L|I`vzhft>cO5pkBo%Z@9mlZ9D?7u68PubRl3)Z7?N_h<9JHj-G zBR?00m2TE%n`CSk+#Idaqi)zur&Zp`fZ6?FT=4Apx;?ucD4@7pAp$jBiW6-?H)i6u z5v9#sc~Ot&8AWYEl3h-(mmi?=EZnzO99tchb)!NjSP3pRi$H$*@u?LJS!oE=^vHMQ zO5Tl9`biOwuQ6acq1O^y2+zR0_y_L!CF!h=npQR{b&wJ@KZF;d&QZkLgI31@LEzz- zKRO*2jz=hm(lI6WyQiGK)%SuMxV>@##FdwW0V7q0;Vt+~{br0$T#WX4#w52K5d<=E zVShdHiR&Y0f0(J zEI)>ik3Yf&s4{;temDFV$tXFVvfnk~k$oMW(NKT7gG;RO8Oxz`^Kw|jJD4YijO6nG zu1QAEpuKSjlklyHH?`Q`WQ5-YoyU3Wu#&B}WMjCkJZImYGzs?@6i=QHBTpGO{(H5> zw4_26{C6_VyalEx9p|8F%E1Eg(p05x5|>1{%i#9ZTN->cSHjb?CHyzy%d-x46q5Z7 zeKvGNM4i`U6a5*x=3XC|-# z8>1E4WmVde4k~O*3$H7-FO5{uKG6kMmHLu2NMT1=X{%O3^vM(+ka+3;&v=|bx7~I} zn*aRg|Ihhv=bZ1HUk_e=r|r*fw}XOjv^ANCZlkEbVaNR3Mdq1C&A0XEc}lDiS;;PP z@Y~?G!_UL-kn9sK(RqWx`dsuiawCI#X7P4B7MSB zl-ogGanX9B41Cmzl`ytS@|JXV(GPQiJ~04_YTb|>Ob1~?`5Y6fSBL0N)Hdc2ZBtJ% zm0R{&DC!!O<;X>_MvF{}nx=EMX(nfA~43*_eBb`}ivI>1#V6@s$R`ZVH zHwVXDCYNqdHkf=;FuAo>zodRuiif7YS)JMqgQJva%el_GF8|JG&(LS6cP*pT85)8p zElaXpFJ6b*-VzA9l-g1--gSj=sXuH9RO;5&tb}}VAM-QoTSvvQuy8Q0#K+>YB*>C1 zCo*ZFEi+DehZcIRr|3Lk zAdV2Dd4}g?MVcO#6a{9>nzP7_%*{w2)0%%$QeskioXqLYVzNX|C&nesC+lvY4~~@B zNonqEs6U=aNur=+^jOS5K=o<>VL|f^`$mQi4@6?ak-nkGNbE>tG^+lV-NxAUnNsUeJ9?gk->%u>uk7ggit~33o8zksx@IQf?X}j;*C*gl;RwMVinkEot8IDQWy< zW>$e}>q{?V0fcp*%Gu`f-5^8&k>3Y!xiyDReQj!f>iYR* zfBTBRbIITN$lI{u-Mr-8yx88mlE0xreH$yBYi_xY)dFIoNfX zyX+}g)ho8^T@jk{2LDM}_@2MJe2bNP?!@m5FP8T{bRAf&-*9vEqtV4p;pO`7%l$u% zei(gOu))BW&!e;&vUd!<2`ODXz#MF(K5gv{N1CbojeJC~+~2$b(CPT&U67T>%iq2R`be6QpYSmL})1h_!T*rT!YXWr08J|$^ z@;3*~Q}q8p>=|inDw8=0NQ~h3D!PT#I9sotbA121Q3GOW<9vG|7pF61<1WRwQ^SYL2%N zOm*xd_$bh9$BklXWe^smKRXWh$!WNm$ab8xQ{C+p1|6gaJA07q!daeID`W(Qz)rf5 z=plX!2eP^?8Bx_-NkJfC^_;UIe+YyU{BoZThy{W2*IbBxZsQA#o8M1=;oACjuozkL zM!qpD<{O8ucwRhrlt3}O9PGa2`KvRy>Zx7~?pyZkU$pNpSncfYRj>b=XWp~oZC~=X zKXO%EIdQ82{Q4Z1*aAm+t1h`-Jg?Oul!3rk5kq_LHr}mRERQ^N^_MVo?{fXV9|L}} zIO-+)ypD4D)rZdLKCJHhyeI%izCF}EE8iEk+;cbgbz1IqvdHh@BSFi3E^x5k za=%_ceor7$X?ak|MuH0w*DAeBU-Ln%CqO4un}ng!g7`p?XUF7mcy{1>1YQeb3?3U2 z83z5&7mj=W!H-q}`vlSC3EooV2$Jt0ISNGMPtC>&Oi0Z=If24sNCr@2PbDTKB{3}} zuKom`D{f*%6$U|ABtz;isxMR@KvoZr11@CKtTU;2n!K%U4@LvWK_ufPT-+Z<*!AjB ze?3PMApgz6AA*mq-58U`jnB$TW}3vI6Jm@&zK05SI+IqW$Pa*zeoTdE-JJ~kio)Ht z+7RT@z&mO6Wy4N-WT9ijdB&+{iIzx8{e9z@WSoZqk`dt$kT*f0u>%BNQ)CAY+KHqS ziGHx~c!X>ugMt|(dV;gaDM;{rN$?E3nw)x}Daf4z@n(AAX47Ma&Z!@4+?LmUd&Ux} z6uk0cGZ~`9Q=|h;U>rn1@>Ky?NfuQ(AR0VO^yi1+6;}dFYSc4;+rTKEM2hxfvm`~{ zgJH*LwX=0TABD1EO+rjG8miE2v6z?{kHvJH&>=+Q#0);E2;NJ^QtPXXt7^an?jDUD zn^PoNvx*5QBOY>nr|D}+a1}HQJkbc&K=)3jksG1lrZHl*_rN=HHYM#Pmq3GSDBDzd zQ|m+x`XKrMl hPB88C?psoUg62+z-u$WM8TOuz(Q0j5-Z}za{{@tZp|b!0 delta 2013 zcmZ`(OKcNI7@pnr?s~m;;@8Fri4!LwAxjE@f3EY6 z>O6`#AXTcG4pOOv3hJQ}f_U!3a7U zal~bWE{Vf_BWi>%al?VKUIo1dofH%+RXMZPdayVWZB}RAY^^kqTSa!w1?7 zuQxJ@yR8QXj~(5AxIZ!2zvpQGvBZJ?AzLe}PV~q39XfQNY+@gC`y*22cCc2#6kx7U zj_v3?*vT2(C|3{+-VmE)oP7v32m5_tx&~eD9;2Tzf&Cv zduHW1d77Ih=Y=6M&FO0LBaR&xUt01?r#SYj+$G_ysLD3F_o+Ulcofyx8}1%;K=;^E z-lFLVlO0joPBnnZ7EMZX)K&)_eZM0p{pVBWgq6-^bvJc3#M@C70b7l(?ri&0V`l1H zZqiER&3qodusJi2d0~&0`G5{23^V^Fzyc{g#{~5%=V9Ndo9b&pb+NCvZzs94GaBy} z?yiZ%Hw$++i}7yO?QM$iV6O}rh*MBM7PO6ZtTWEwa%NI(iE?0&SC#Aw) zX*4)XWluYs-TbUDCron|d%P~npRHocRgvmv{e+TVsO-0zaaS_T)>mI=aV^3&$s*h4 zKe^#TV1IThnMoV5iR4Tsmo$1~0~bOST|PaUOqwK>#V+7-~J;#%oNz+L= zZ3YExWuFIPEiSqqg-&!4%_%c$QF!kp?*fRypdD;`uraU&#Dl1R9RPCM!2Sqo+WqG4 z6@d?UE{jEp__fR4rw>$(oeT}M#=z}jyH0K@aZ&gr(x(V_lo-%EdL&MSyF`qOOOHYi zxZqZBq&ooeSOVv~@3bay7YHLuziYoJLjj=(R~!JHJP_^Wc zNp#LRE|0%T%C`-EW`cRp*wt6FtFB>1XDnKyCRj%^8y^i^fnA z57TtYq%G*wim(=;4FT(`%hXB8E2wZTcp6y(A&B6V`Cpd{rKp*-%tYzsCMKLSMlfN& zk)=KBAM)H7>)g;)3aIK#I+ICfM-vk{Y9%wY3HOa5;A{WSs9bauO=AGITz>^QVV zMolYG%BV@p`zI!8hK_@^iDN%(Sd2Qopz{L*dW|h75=JhSNKhwJd@j%4+}JR0OR$ZZ zowDWRL^^TKoT2y@O8t+&3tK!hW0`r|Wuzgk*yI#DiNs@OsrUc?CM@L>8f!fpho z72G?^kzoDX!FTHx>z3;`-V1asNSCVMxzx2> z<*zI6Da$QA_iB5WiB@!Vb6dXm`HKRqKlwvN5ms=l3KV5zZcs diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index 250dae28f737027b76580c6f11eb2c943b7ec3a2..e42c0a0885f92887a01904f060d5ef69c7c85927 100644 GIT binary patch delta 67526 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 diff --git a/alembic/versions/956ea1628264_add_stripe_transaction_metadata.py b/alembic/versions/956ea1628264_add_stripe_transaction_metadata.py new file mode 100644 index 0000000..fe28920 --- /dev/null +++ b/alembic/versions/956ea1628264_add_stripe_transaction_metadata.py @@ -0,0 +1,76 @@ +"""add_stripe_transaction_metadata + +Revision ID: 956ea1628264 +Revises: ec4cb4a49cde +Create Date: 2026-01-20 22:00:01.806931 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '956ea1628264' +down_revision: Union[str, None] = 'ec4cb4a49cde' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Add Stripe transaction metadata to subscriptions table + op.add_column('subscriptions', sa.Column('stripe_payment_intent_id', sa.String(), nullable=True)) + op.add_column('subscriptions', sa.Column('stripe_charge_id', sa.String(), nullable=True)) + op.add_column('subscriptions', sa.Column('stripe_invoice_id', sa.String(), nullable=True)) + op.add_column('subscriptions', sa.Column('payment_completed_at', sa.DateTime(timezone=True), nullable=True)) + op.add_column('subscriptions', sa.Column('card_last4', sa.String(4), nullable=True)) + op.add_column('subscriptions', sa.Column('card_brand', sa.String(20), nullable=True)) + op.add_column('subscriptions', sa.Column('stripe_receipt_url', sa.String(), nullable=True)) + + # Add indexes for Stripe transaction IDs in subscriptions + op.create_index('idx_subscriptions_payment_intent', 'subscriptions', ['stripe_payment_intent_id']) + op.create_index('idx_subscriptions_charge_id', 'subscriptions', ['stripe_charge_id']) + op.create_index('idx_subscriptions_invoice_id', 'subscriptions', ['stripe_invoice_id']) + + # Add Stripe transaction metadata to donations table + op.add_column('donations', sa.Column('stripe_charge_id', sa.String(), nullable=True)) + op.add_column('donations', sa.Column('stripe_customer_id', sa.String(), nullable=True)) + op.add_column('donations', sa.Column('payment_completed_at', sa.DateTime(timezone=True), nullable=True)) + op.add_column('donations', sa.Column('card_last4', sa.String(4), nullable=True)) + op.add_column('donations', sa.Column('card_brand', sa.String(20), nullable=True)) + op.add_column('donations', sa.Column('stripe_receipt_url', sa.String(), nullable=True)) + + # Add indexes for Stripe transaction IDs in donations + op.create_index('idx_donations_payment_intent', 'donations', ['stripe_payment_intent_id']) + op.create_index('idx_donations_charge_id', 'donations', ['stripe_charge_id']) + op.create_index('idx_donations_customer_id', 'donations', ['stripe_customer_id']) + + +def downgrade() -> None: + # Remove indexes from donations + op.drop_index('idx_donations_customer_id', table_name='donations') + op.drop_index('idx_donations_charge_id', table_name='donations') + op.drop_index('idx_donations_payment_intent', table_name='donations') + + # Remove columns from donations + op.drop_column('donations', 'stripe_receipt_url') + op.drop_column('donations', 'card_brand') + op.drop_column('donations', 'card_last4') + op.drop_column('donations', 'payment_completed_at') + op.drop_column('donations', 'stripe_customer_id') + op.drop_column('donations', 'stripe_charge_id') + + # Remove indexes from subscriptions + op.drop_index('idx_subscriptions_invoice_id', table_name='subscriptions') + op.drop_index('idx_subscriptions_charge_id', table_name='subscriptions') + op.drop_index('idx_subscriptions_payment_intent', table_name='subscriptions') + + # Remove columns from subscriptions + op.drop_column('subscriptions', 'stripe_receipt_url') + op.drop_column('subscriptions', 'card_brand') + op.drop_column('subscriptions', 'card_last4') + op.drop_column('subscriptions', 'payment_completed_at') + op.drop_column('subscriptions', 'stripe_invoice_id') + op.drop_column('subscriptions', 'stripe_charge_id') + op.drop_column('subscriptions', 'stripe_payment_intent_id') diff --git a/models.py b/models.py index c0b50e0..930ce27 100644 --- a/models.py +++ b/models.py @@ -238,6 +238,15 @@ class Subscription(Base): donation_cents = Column(Integer, default=0, nullable=False) # Additional donation amount # Note: amount_paid_cents = base_subscription_cents + donation_cents + # Stripe transaction metadata (for validation and audit) + stripe_payment_intent_id = Column(String, nullable=True, index=True) # Initial payment transaction ID + stripe_charge_id = Column(String, nullable=True, index=True) # Actual charge reference + stripe_invoice_id = Column(String, nullable=True, index=True) # Invoice reference + payment_completed_at = Column(DateTime(timezone=True), nullable=True) # Exact payment timestamp from Stripe + card_last4 = Column(String(4), nullable=True) # Last 4 digits of card + card_brand = Column(String(20), nullable=True) # Visa, Mastercard, etc. + stripe_receipt_url = Column(String, nullable=True) # Link to Stripe receipt + # Manual payment fields manual_payment = Column(Boolean, default=False, nullable=False) # Whether this was a manual offline payment manual_payment_notes = Column(Text, nullable=True) # Admin notes about the payment @@ -269,9 +278,17 @@ class Donation(Base): # Payment details stripe_checkout_session_id = Column(String, nullable=True) - stripe_payment_intent_id = Column(String, nullable=True) + stripe_payment_intent_id = Column(String, nullable=True, index=True) payment_method = Column(String, nullable=True) # card, bank_transfer, etc. + # Stripe transaction metadata (for validation and audit) + stripe_charge_id = Column(String, nullable=True, index=True) # Actual charge reference + stripe_customer_id = Column(String, nullable=True, index=True) # Customer ID if created + payment_completed_at = Column(DateTime(timezone=True), nullable=True) # Exact payment timestamp from Stripe + card_last4 = Column(String(4), nullable=True) # Last 4 digits of card + card_brand = Column(String(20), nullable=True) # Visa, Mastercard, etc. + stripe_receipt_url = Column(String, nullable=True) # Link to Stripe receipt + # Metadata notes = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) diff --git a/server.py b/server.py index cbbbc48..8cf51a7 100644 --- a/server.py +++ b/server.py @@ -227,6 +227,7 @@ class UserResponse(BaseModel): role: str email_verified: bool created_at: datetime + member_since: Optional[datetime] = None # Date when user became active member # Profile profile_photo_url: Optional[str] = None # Subscription info (optional) @@ -2476,6 +2477,9 @@ async def activate_payment_manually( # 6. Activate user user.status = UserStatus.active set_user_role(user, UserRole.member, db) + # Set member_since only if not already set (first time activation) + if not user.member_since: + user.member_since = datetime.now(timezone.utc) user.updated_at = datetime.now(timezone.utc) # 7. Commit @@ -4525,8 +4529,17 @@ async def get_all_subscriptions( "donation_cents": sub.donation_cents, "payment_method": sub.payment_method, "stripe_subscription_id": sub.stripe_subscription_id, + "stripe_customer_id": sub.stripe_customer_id, "created_at": sub.created_at, - "updated_at": sub.updated_at + "updated_at": sub.updated_at, + # Stripe transaction metadata + "stripe_payment_intent_id": sub.stripe_payment_intent_id, + "stripe_charge_id": sub.stripe_charge_id, + "stripe_invoice_id": sub.stripe_invoice_id, + "payment_completed_at": sub.payment_completed_at.isoformat() if sub.payment_completed_at else None, + "card_last4": sub.card_last4, + "card_brand": sub.card_brand, + "stripe_receipt_url": sub.stripe_receipt_url } for sub in subscriptions] @api_router.get("/admin/subscriptions/stats") @@ -4766,7 +4779,15 @@ async def get_donations( "donor_email": d.donor_email or (d.user.email if d.user else None), "payment_method": d.payment_method, "notes": d.notes, - "created_at": d.created_at.isoformat() + "created_at": d.created_at.isoformat(), + # Stripe transaction metadata + "stripe_payment_intent_id": d.stripe_payment_intent_id, + "stripe_charge_id": d.stripe_charge_id, + "stripe_customer_id": d.stripe_customer_id, + "payment_completed_at": d.payment_completed_at.isoformat() if d.payment_completed_at else None, + "card_last4": d.card_last4, + "card_brand": d.card_brand, + "stripe_receipt_url": d.stripe_receipt_url } for d in donations] @api_router.get("/admin/donations/stats") @@ -6314,23 +6335,67 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)): donation = db.query(Donation).filter(Donation.id == donation_id).first() if donation: + # Get Stripe API key from database + import stripe + stripe_key = get_setting(db, 'stripe_secret_key', decrypt=True) + if not stripe_key: + stripe_key = os.getenv("STRIPE_SECRET_KEY") + stripe.api_key = stripe_key + + # Extract basic payment info + payment_intent_id = session.get('payment_intent') donation.status = DonationStatus.completed - donation.stripe_payment_intent_id = session.get('payment_intent') + donation.stripe_payment_intent_id = payment_intent_id + donation.stripe_customer_id = session.get('customer') donation.payment_method = 'card' + donation.payment_completed_at = datetime.fromtimestamp(session.get('created'), tz=timezone.utc) + + # Capture donor email and name from Stripe session if not already set + if not donation.donor_email and session.get('customer_details'): + customer_details = session.get('customer_details') + donation.donor_email = customer_details.get('email') + if not donation.donor_name and customer_details.get('name'): + donation.donor_name = customer_details.get('name') + + # Retrieve PaymentIntent to get charge details + try: + if payment_intent_id: + payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id) + + # Get charge ID from latest_charge + charge_id = payment_intent.latest_charge if hasattr(payment_intent, 'latest_charge') else None + + if charge_id: + # Retrieve the charge to get full details + charge = stripe.Charge.retrieve(charge_id) + donation.stripe_charge_id = charge.id + donation.stripe_receipt_url = charge.receipt_url + + # Get card details + if hasattr(charge, 'payment_method_details') and charge.payment_method_details and charge.payment_method_details.card: + card = charge.payment_method_details.card + donation.card_last4 = card.last4 + donation.card_brand = card.brand.capitalize() # visa -> Visa + except Exception as e: + logger.error(f"Failed to retrieve Stripe payment details for donation: {str(e)}") + donation.updated_at = datetime.now(timezone.utc) db.commit() - # Send thank you email - try: - from email_service import send_donation_thank_you_email - donor_first_name = donation.donor_name.split()[0] if donation.donor_name else "Friend" - await send_donation_thank_you_email( - donation.donor_email, - donor_first_name, - donation.amount_cents - ) - except Exception as e: - logger.error(f"Failed to send donation thank you email: {str(e)}") + # Send thank you email only if donor_email exists + if donation.donor_email: + try: + from email_service import send_donation_thank_you_email + donor_first_name = donation.donor_name.split()[0] if donation.donor_name else "Friend" + await send_donation_thank_you_email( + donation.donor_email, + donor_first_name, + donation.amount_cents + ) + except Exception as e: + logger.error(f"Failed to send donation thank you email: {str(e)}") + else: + logger.warning(f"Skipping thank you email for donation {donation.id}: no donor email") logger.info(f"Donation completed: ${donation.amount_cents/100:.2f} (ID: {donation.id})") else: @@ -6360,15 +6425,26 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)): ).first() if not existing_subscription: + # Get Stripe API key from database + import stripe + stripe_key = get_setting(db, 'stripe_secret_key', decrypt=True) + if not stripe_key: + stripe_key = os.getenv("STRIPE_SECRET_KEY") + stripe.api_key = stripe_key + # Calculate subscription period using custom billing cycle if enabled from payment_service import calculate_subscription_period start_date, end_date = calculate_subscription_period(plan) + # Extract basic payment info + payment_intent_id = session.get('payment_intent') + subscription_id = session.get("subscription") + # Create subscription record with donation tracking subscription = Subscription( user_id=user.id, plan_id=plan.id, - stripe_subscription_id=session.get("subscription"), + stripe_subscription_id=subscription_id, stripe_customer_id=session.get("customer"), status=SubscriptionStatus.active, start_date=start_date, @@ -6376,13 +6452,48 @@ async def stripe_webhook(request: Request, db: Session = Depends(get_db)): amount_paid_cents=total_amount, base_subscription_cents=base_amount or plan.minimum_price_cents, donation_cents=donation_amount, - payment_method="stripe" + payment_method="stripe", + stripe_payment_intent_id=payment_intent_id, + payment_completed_at=datetime.fromtimestamp(session.get('created'), tz=timezone.utc) ) + + # Retrieve PaymentIntent and Subscription to get detailed transaction info + try: + if payment_intent_id: + payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id) + + # Get charge ID from latest_charge + charge_id = payment_intent.latest_charge if hasattr(payment_intent, 'latest_charge') else None + + if charge_id: + # Retrieve the charge to get full details + charge = stripe.Charge.retrieve(charge_id) + subscription.stripe_charge_id = charge.id + subscription.stripe_receipt_url = charge.receipt_url + + # Get card details + if hasattr(charge, 'payment_method_details') and charge.payment_method_details and charge.payment_method_details.card: + card = charge.payment_method_details.card + subscription.card_last4 = card.last4 + subscription.card_brand = card.brand.capitalize() # visa -> Visa + + # Get invoice ID from subscription + if subscription_id: + stripe_subscription = stripe.Subscription.retrieve(subscription_id) + if hasattr(stripe_subscription, 'latest_invoice') and stripe_subscription.latest_invoice: + subscription.stripe_invoice_id = stripe_subscription.latest_invoice + + except Exception as e: + logger.error(f"Failed to retrieve Stripe payment details for subscription: {str(e)}") + db.add(subscription) # Update user status and role user.status = UserStatus.active set_user_role(user, UserRole.member, db) + # Set member_since only if not already set (first time activation) + if not user.member_since: + user.member_since = datetime.now(timezone.utc) user.updated_at = datetime.now(timezone.utc) db.commit() From ece1e629137b6a26ee4c423e6542208ee8000acf Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:10:02 +0700 Subject: [PATCH 3/5] =?UTF-8?q?Was=20reading=20from=20.env=20only=20?= =?UTF-8?q?=E2=86=92=20=E2=9C=85=20NOW=20FIXED=20to=20read=20from=20databa?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 8cf51a7..a10850c 100644 --- a/server.py +++ b/server.py @@ -6101,7 +6101,15 @@ async def create_checkout( # Create Stripe Checkout Session import stripe - stripe.api_key = os.getenv("STRIPE_SECRET_KEY") + # Try to get Stripe API key from database first, then fall back to environment + stripe_key = get_setting(db, 'stripe_secret_key', decrypt=True) + if not stripe_key: + stripe_key = os.getenv("STRIPE_SECRET_KEY") + + if not stripe_key: + raise HTTPException(status_code=500, detail="Stripe API key not configured") + + stripe.api_key = stripe_key mode = "subscription" if stripe_interval else "payment" @@ -6176,7 +6184,15 @@ async def create_donation_checkout( # Create Stripe Checkout Session for one-time payment import stripe - stripe.api_key = os.getenv("STRIPE_SECRET_KEY") + # Try to get Stripe API key from database first, then fall back to environment + stripe_key = get_setting(db, 'stripe_secret_key', decrypt=True) + if not stripe_key: + stripe_key = os.getenv("STRIPE_SECRET_KEY") + + if not stripe_key: + raise HTTPException(status_code=500, detail="Stripe API key not configured") + + stripe.api_key = stripe_key checkout_session = stripe.checkout.Session.create( payment_method_types=['card'], From d322d1334f64f8317d849eb81cca0187dc19620a Mon Sep 17 00:00:00 2001 From: Koncept Kit <63216427+konceptkit@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:35:19 +0700 Subject: [PATCH 4/5] 1. Added member_since to GET Response- - Endpoint: GET /api/admin/users/{user_id}- Now includes: member_since: 2024-03-15T10:30:00Z (or null)2. Created NEW PUT Endpoint for Admin User Profile Updates- Endpoint: PUT /api/admin/users/{user_id}- Permission Required: users.edit (admins and superadmins have this) --- __pycache__/server.cpython-312.pyc | Bin 295856 -> 299834 bytes server.py | 83 +++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index e42c0a0885f92887a01904f060d5ef69c7c85927..8405405d574aa3504dc8bce5440dcbb2ad6230c6 100644 GIT binary patch delta 33990 zcmcJ22YeLO*7)9;+1+e5Wz&1I=>(EMKza$i6G}h#)bLr6{d!v4^I8uxj3?mGVd20=hVrSTH5fKfB=?^<= zHZWdwvk)pcO74q`iYRs7;z)M}&6OPU*BW=LScd7Fl?|2%Rt5+$<<2c0gbE$PRvn#6 zrOGEgrpnpHgSSA3_ox@o$00kH@2TD`D7Msym{Mo02fIdxz0HfQr1-=D(h3jKA|29p z4XMH>Q1OrRiz{{3c@UTA5O*MA=?K0tO9)|0QAqz%=SoM5peE%{pESw2dQY`-#uBcK zH3zXOWvr!#vcxCBzXeHhtXOMQZnJa}SFSZEFItjfcQe&Uj#8&-uHaa`R`OcyDYTk# zjfZ4Z6c7Q7wK|cW){C^pBhnh3Nc()pSvB6bD}L5wwoX~;n;x?s+M%VyJXbKKLStzdBaIPL`&smY>agVU_5yHQv7 zSz=VEn*2VLNT-dW{Nk5vz26z_yfq4J2zKA=*QwJ3&KPGLK06-trdqV{ac~{+=Sk1o zltaN8sha?I3)O1DH8~#A;nsVV2z5NHQ$hqqCOICt&z5N1j0RQg0g)1Ti7K)(F2LX9 z#qcK#k6W9R6#?TST9`?W?K);JpkT^h0e;GzzOf0dYB?U$;kmp*r8pkf2?dsId2;vx zdsWCMlnDv;E)FDz27mGzjeQ+Eb;J+oOds-83q3Ejr$Ob!U9Eg*x32at=15NP?GD>@py4G!@cwO8!nT{UV)mFBp_L=CCM zHBF9VI(0ulbz_dVkahT}|9NHl`+c^AK_(rq6JD-Okou6Ub8TnybDY#M`G{&lns#) z%F&46bdd<0aek;Ha8^g4QAdD%fs*(9vV}~`uUCq@bws|>5&78bsDk5b<<*EJ#bygi zM#;Z%9@G5Q@vW}Tab2Br$hY_T7Je1& zv5`5R#?p@}K8`E8`lm=*YfO529n&pKqcmzh!<6T*H$(Mp{U=tTQfhy95cJJfMi zCz=|9J(VBSRsKy^`BQIYWo)do$>Z9ZJYHAv99B$d zzv4A*W!F_a?-d-4k*LlH)G2ifd&p0MZkt@{yxl7}Hc+yT%s-G!=?JB&Z&*mmHDcfd zFHxO1tn-pU-BWcWE`SQ=6wj2zoz#tH8ZDlT{L+;T$*Ib)^50NhG%0^edjC-c-~Fi#F|Z z^)baSJ%SBUo#{t!gJ9zh)#3lDHDS9Phaksv`W(^0vqYq9p^CMt1l31~VsP>EpwrJ&Zqjk0Z(Ai;3 zJC(AS1UIy zYemE4HE0*0U*1}q>puZ*MT=pTn9hlV9k0;zK~?ErnG@Zws+9<4&74^auFq22T=2B4 zCcrbD?W54sszKHNR1R!b-JTtE10S6^^>VbBb0L`%dFXq+Zc7hnSiI&O0Q49Cr`oiu zTQm-(OO&hGiEJr$d-P`2zRSpcZfWhhP+AK!q-z}*9cOtfi~DL8@4MoEC1VWx3q9c5Ox zbW2O~Ny>fQvSPetyM@-%HwP`7yvQKm?X74Bc-tDg^jhae*a-yk#!}~fup!snEs6yK zcjJ0bL0SjEx}%M>cebKv)7nL=mg%Wh>QuanB17HAC^%k_W-PW|S=l2E{ks)6<-YEj zSkRsr_qGxvL=%JEgHcLiMIw27|9kEND8(PVy+jRvKg?ifyJJu!?qty&Tx9b&CF42f}eL7UA*I$K-O ziSp7xIUj}DCoKGb^3E-{wL*?|R(lRY5P3Tw*TIIq7kNi3o^bfh#b4N( z1yXU??0c@U_cN_zi*Kd%Uc~ON?|ZskSRk3;nXB)kx%zY31Ru)mNJ(8%vFDLaYUu{% zw6|)x`c}LX!H964;~T0R>JtPLS6Hj${H>+cpv~Zb1eDpY9gzT+6c~jTK<*39sz-2P z;!+~}_KrIMse>(n1h7(pbE@18;Rb=*1R*pDFkWn-J=Acop-7kq|IZ9D7?vAq4I6~g z@%&P`5Usr0*T1eqMeIBz{?ZNx#5#Cbz6|6#JYv`)w2)!A{zhS5xhCh;YjIK8(tI`6 z>7mmfC@evI;`!PRhB5ijJYemChc~c2uPd|qMX)3Fi~21YtxTwiQVuw8t}~%4!{)xZ zAN)Fm>3&Y5vn9{RljpC?vv~3XATQ8U*VmJ0)8$z`c|p3mexAHwEw9eu@5u~-%uo-( z08d_+E-%oN7p_%y*f0+a7~$}BSRFxof?G}*9U;(nJr>VDzoy96M=k$;=#AUq|KBt$lT0UhkC=GKWb7eP3u&hJ7d~33u z-nO@XYIXc~_B+N9jca(KSacs5n zZBd-EWnF;Z1wgyia7r3*(w?I1yDde@b@WxTX4#ZGZncWXq)y7Cv-;(n%*ttyVm3|P zR`k%!V^YppW5Bw)Z2|j+9yj*BBuK`f%c8;5hf>5=9->TG6IK7u+m$9Gr~rLVOLk~J{2 zS1)Et;GWp}(OXTTEg#EE5llfaRhhj#)ieW=%ldJ4hJ`3wxA*8i6N_dcxETS_9FC^l zh~P#9Hvz~HF z4J5F9fpT_Vy73-J{#oz;T&%>(l#ct$*aC&`?`BdU+a}gG?EglvYHB~A96pe0d=Sbq z>n|L*lUdgyp<59=tgL!5*0>dted~9;_=UvQD!0DY7gRj_T6oIiknIjGs;rn>GH;>m zbQDy|CG$!us^zD#>N5!TD!;v!Zae_VQT6GEwz4GTiUu~=<*5MO)_KmF0#C=1UxtFE z_1_-mCNO2)JDU>fdd6%_vM^N_@j4<f}l(ZX_^)B zI%H-@GQ%>>P=}OtOjuQa`91nTQfcQU;T8Gcgz!P5ywko(jZCI43L!Y?}- z--lXR^&7rCYBp^`HV-Mz3n}aa<&F#U!S8;)kOtQU;=bz!6eoUnbAr|+MTo2!;awE^ zdFB1@?5t7w?z^<;kFlJ*1VgE;Goh0CeOlCVD0Um2vRo<4djKKBPyPUFEKufue>1D& z;|ql(<^1;n#w(CJvHth(KQ*x<%Bd@M8+Ep0hwWx%|DAkf;<+ZWZYi2}+O5RiCgo&CSV-0ufp zUlQW$hyAhA$jbN;fhCx(BHCeoL16t&0^rFDc(%w=*dd-TvP`27WHr0iitGU?;s~H# z4IH+}34=&QB@QGDD;)9^-o=NlV>9?c9|i{Ff`v^@2|!A}BkA_+q?(AYu&~I)Ypik! z8&`A9zqrf7;-URcSXj0(1n9=PEWYef$%f*{aAfO=mihcZ0Lx+i{2u|V6NnQY$cjOn zxoJEO4S0|B-~9+DK@U+e(OX1fd*tJln={tl^LzWCU|(GX7T# z8|He}#s(PKe*Qxkiwh!yp93Y`lEYbD1h$V2X9>b@jE^77!uz-_CFOVm~3HN^$IQ$K*yBh@G%KNG6I-V8st;}8P{sg55%y3MwkH_Zgc$_1I^EpU4!FT zwyCa7;{b+1{s>tdMbLmi4Gxf6=A-Jfw6ojJq?^na3}y*@R|*Rpd)=sX z4$;pepeR5wiGo5W)UZ8g{|EVbxn^3g5VF^^G6GbmqJTx^x(&dE} z04Kv&uTdw;*G3%*%|@97Y10r{{J9*KW9kkxB2BJyIV{|0^>$|H#+IHeDJ&MHpwYD) z%~posH$Jc@>lUWz^#?Ye+H?83o~);_57f$Wz1fqEF}4kBJ$XWZ*2~lj>BjPz{n=O@ zA?o;7kwu8q2KH!H&vnV#2yGOG{0uY-th7DmK@(0JTTT|MLRc-qk&mGG$+3UP) zEQ_2p7F;5N(7sJ%#u3q$Kr3Gw%a7bBC4cHo`Sf6m?M?c zIAxfkQ>)P2{x4oRnGG?@fbw^jYcl&qVnux4G&U%FF4md{z%31%TsTiIhGa$zXJc8k zjXWR?n}j@TKhK%YLbBWV!WAs1fdLy_E=79t5tJctaCtiG(6bWKZmGDix;Vo}v!y%@ zQOI=U?c_0*xlWi&R}_{zCLUD+!KFo&KT!i}?8nDG`sOLis9X1hgJ%ik|zgIOvM zaIhR(9g-q$>Hx{dJ7B(o6KHuQ7Og@+!4F4K=qa_Bv4F30uwlmAAwA6Xv4edt%vyo@ zSh&ox)LAIohYrghlHo6}#_UWqF-6!l__!7UGIkrw3gWVmRo3gMP&oJ(rd*7yM{ zAwkrS#}&h{e>XfiT?NJLZK00jK>ul1&OnuMmX|T9NOjsw1ZcK!JvuV2B=q)0irC%w z6Z2VT<9#5|(w(Q~GZBUvcLfbIzf`b{o~>=?_ARL{C|W4XI2|K92C4F8d*v&|)FwW) zlEtMxi00r3uG1iF&=6Z8tKRi!H9NvO;|%A#!7#xy^*IfX8G`%>{J4Y43zxtSNu`W)y*W#3U@EtT z-@lNh8Fv7IM%QZ#*)}1OW=vR3oiX*mR5zp|d8QwH>tXh7Ehpb>}Ci3vrJa+}l%(w{=vGYJkE-EZ5lx1pW>{GCC z(>m=MM31w1>B_i`2?P(XU^&L;ApNWBpDS3g#Pa#H^BqT+(N zp(SN79LoEl#BHivSW~sIMn1*w*~CU0zks~CuG5>?PomWVmDa%jm%}9`@h%4+=n^w{ zLztN0TEB(;UEtfcGKtUG#yZ<8;DTGNSULcZ+x*FKnEwC+=Q*&I#RNFQH=Cbe`3482 z_@mok?RkVZZeyVm`k>bXjmox9BYDhrmdeb0&~}z?dj}{}(4eh?3_EW)$x=Aq&axzQ z1s=PdCG!j0na%n&YD{kV4re=9G+V%9cd&HdZy^6OADBs5dBbittgcy#@-v`?HW@5o8yviHV`Z}$pM4CL&<&IMtc|mpE%@wf zu-H8KR($p|Sb{g)!250N*X)na0R~Ip28mlXTABm#+17#|8r>X(8NphGkSLz|7)!0g z>~Mo6cGKjhxNePc-I^mXC(>Yv+vI49@7@^Sy*UbVqV+g2m=mkdiNl1fig5NvA)I%=DA6ze3}J{rkehI z)p3?q=P+*;@(s_3i&=)xvUK&Vp}o@sdxt_ijIiUGLxxOq_wU=*-6G%yrUTfhWxYP3Fi(b7X@#*R7WTXfP&kXuUg(< zwx0}x1>DA&km-w=b*t(c%t_|H%-U784d#TCp)q{y#<7rTX>l~eL+{bzwFQPcEqx)E;F|M_pV7=)qtgfuUV?KkN z&u@N#tx&gxj`Po7V6bJ+LtOAv&qugeXRA&IzRJat=ixRAYzveZmXsAhA6irb%kIm_ zjVwq8)g}YW(M;a$aM~$>1tb{^F4yWue=-hi*j}EgM_8A}YIy>swH|LRWu!q=j{`?#47SJ#IO*WKW z;MH%k0qhU{{F^LkEVXVmheRe8=S*2$*r z+NoZ}FRfuA!8nJ4T^%jRFW?(bv0*HpH=SZVO&vj?{=TkIH(TY|Yt#G36Q8p*^HiXK z#{Kwn*q55YfB&3~%+>5lVS-o>N4t`1d%e14bXkBlqApL)JCCn91E&|6z#kHhr2h|-yoNdAvyJMiex(h5H|Ea!0eL<+z5{IDc`d6Xm!^cGrmCZC4#RIe60?f zxWQew7BlE1)VtRwJ2CHF6y$kKeS?7ZdK)oCJKt>^0t@KW%WDDMS=S0Ir47{pwK+&$ zGL6||8$RnCiw(Mjh~DjN@Ba0+W|%T>J;$Pr*|Y=nP2+=%FloCpMd)Cn zP;;E0`Gw_zgW7*({VfL(H4dmMuPV{D7)W@IeUaD&zTsC`(&+*=<`NZM^Yg}ES%PgM z7HRs-eef#lZR!T}GOe!Mt4y@0Ba$j4A14YuOq!Tce1|ARvZ?$f%%|n;DE?1TNHVQP zvX8;}wU7WCrMZ%D6NCsZ3Ee?~4<#YeL@VF{{DLG5w|SM3)qaPn$JMnFKN>w%Cq6Rx z1+CBwaG1Lhc>WfH=SLm^|XYlKP26Irh7L5naz-HpE*3X!q|_6y)}LVtpa zZ1ed~{z7I(ccj$=0Y!vfm{LuLNPX~;<}!Wx=m4Q(EcWYVp~GRog}_=@)yF zG}sUz3{blP+7=d?&x8sY{RbiqI+tkA`N^o%6y*GE?a6Zkg}9;DwI~_WN6tkSnhOoa zM{1NInDUql2XX#Fpb%ml0lE8K9|a0CJ#(Pph(;YCpZ5(FGQm@dLxp(ol-omv@x8qo z;BMoh%K1)5d#6jV8`V}09vCL18b<^5v##D@!V(Eq?9WFD8CK#o1bru;e-kD2WTSX| zw2;z|IMH^R$?Gsf5sfzFwL=rHCEaG{lByE8G6F;GifAE89gQUUC9F=KHJCpiE%Y}| z03rTxU5OU*Ejct4jP>fQ%7M*24)d_1O{N(c>>1N&qmKlO5N|O*m?F$!%Xn<605=!- zkW`^tum4(hIS<)6_})|@CyW9JhLg5YB+iCi&h1lJROGCJqZHWSN=y^NnUnWQ6H=ln zdZ>zG6mK!4t<3rIF20CSIDs) z!mKwCypG^7zpF1Ocb3=p71ERQAS;8pjj;J9*T{r=3Qc_2i~k1yqp#4Hy~=y{6Pzh; zVg3;WZzFgI!J7!)MQ{|sd;Il&!k9>m5$A0{YqER+vSBl_jm?w!&HV-6U|P!h+Dpo- z=tRjbALEPr3)x{f?702a?1IwDIRzySc^ZGQzmNm6f7f3~RBt@suqdAd25#S>B}Fxp zoP`e9$U1?7H1X^KLV}g%LB|knDxV4&BU^_$x5Zhp5Drq{%AdRqvFg#vhY>u4k2o)d zLxjr3KvoN<#gMDmcSiG51B5X{enV_+WXFIUL5(!f3iix%+ba)ns9uJKW>kdrnIvS5dwY=p*WLkMPgjwSLeo;pxS4k3wgLRXz7T zi`giLT!lc>g!Ve8L!R50ya;qHtO5C}#tE5qWHIXVwC}kRtG0JMa9!#YV3E3dP_Y10 zWV@*#&-M0r(ex6SshY>+!6?5LDh>B{jmi@$jC7g{;|H124mfSckNNy$z7W^#x&nFC zl}Q<|Kp7Fdpjb#qNkpe1fyp)7rNie>7UI&VK1O<3BVv?qMGdu>zlQIfEOa-nhujL+ zxyiz7J~p+FW4)UY7j7NQ6#B5P{KuKX+|(B#KZD5?Py$CJ-vH_cyXodsFK0$ z3(B1}g>XT(kPNac@+K&lfCcs;`8UGu6&`jt9k9pVQAhypCbN^o!8V|VpqO)*ZKz@N-MFQy!kS{Y ziDoI)a0pXTR#Guvcd4Z10vwtgL$cd{+@Pri!$*xBR**kx`Y?GR@_Z10TdXOsa{B-% zK$7ykm={=BQCPOLx+Ji=090RCRwLhn_%zSyV%!dC?pj+Y{4TIO*D8mwUQ}1xw?QT&uXwoyQPH-OB`-#%9K$<gp+($b4(HrLa4T@R>T2wI z1$c)*uH|c22&p!8&VbeGkoiG=aD|Xa58d{OrZ z+3KWDbr3%39^rLpzp#5D;{3=b+$%&g7oUHxFcW4=@7^oKw>0Ya_X>mSG?Ss1x~*`a zRyL=wXg-b)q)8$gB?*Ol8Nn+Ex?+?eR2WDQyFL5rXw6xwo^$s=T3Q!}bxJ=NsxXsc z0JSi+t9S7NBOCRTw~=idyC2~H*dV09@Ef>M$Tl7ajB~ER8-?9Q*hK&A4Cqf? zX7s>9np2t7lQ$4~ zXeu{>fFlB3Z*xJB)P=n&A$M+uN%$eYWwVgjbw27j3js~>&@8a!qz;6`=(;bN2Bd(y zyC}5pF&B_ICY79qm;2}n}z`eutTMzUhCpayu_s;sK& zz+c=0r_vQWgzi?&A)I{g4k4rMs{Akh?GC|i{0xX~b@@Fe_%PEzAhs-$+u~THtLx(s zm)qVXo52U{6pH`#&M~y!B~*MOI)tX;U;LAuLZa~uuzSPBo)Y>>dD9M z5qg<>VNR^T2lZqTyske>4)##g6KmQdWcYpo#J&RCR{zZZjA6rF6P^LGZDpR%AAU~A zF%1T)8DV%oGsAWmMN32>8K!>YzRwHknTSDCEodLoK*tZ!reXP?n1ZWiGoKeanZ5!3 zqRn;B^FpFo^*vP^{^CI>rO{yqzi?22NHyo9N)>0*Sn9n{}5>Z1CX3(bA5A2 zC}_uxro15x?1k};#xGcBw`1^X3HYP$)7ubo7-A!+a((!QP|w)yeAQdRB;QNGNJg-R zpMFaiC|nYiK{b)Co=1cZFt6aV-w{G#^0e?BVGZ0@|MeXq297Ah-W4jCm9KqQI07dT zW8M=+u^s$@_k`7IqlNRh_l0uy6tDe2u=CyT3q@vXOoPP3J`m=zy;yKN5Rg6;9)z2B zk9;W1zxijBgRacrS2yH8@UaY=YcD?jiI0CFV4#*PAP^B)F;Bvj5kVLtn=xgfQUw0I z@FO9__X=VnxW~2PBVjocu87LSHIY25LEvm7f2Kjm7B(`a?v4oGK&*z~W&US_&|L_W z$R^_&g&B;!$#;D$%<~OK90Z4X%rT*_5KK6{>X`5inC+(Hw5@jcap4qP_?`E6VIupO zAN;#8&lZXGXlImUjZ=jS!(0UK^KmDH+gRAn<0k}WA_E-e?k1s!(GI0_9Vpi=Yi9>@+Z&*1B#23P!y)0NEvT z=chu3F$HqJa4DaHx5Qu=x*9N{tniu*wIu%N8KKuGO@M#R`)!nm1Te~0YZfp zWNtk#bel*V52GsWxo&&*Cqdizw0P#2{xZpKILg(3s?Lxw;= z5QHuECwA|@_!v>#+HF5(IIs@&8tqmY@n={{!xMEk&+rs0iAiV=>;0GJh!BAOIh_r_JXo4+E7fou^SE%PrWF~Zmvs-1TIC5h=~ z_B2oT6T8+ugKW|2Zc7o4!|)Q0L)|s)kBkN&$VG0U@?J!x3y-v@C$AxI(JYdIR-!f> zh!ykDo^&meY`h)`Xokj3=p4BX8~uH2;^h@kR!MS;zu3<-7|4ePyTa6a3gS4qKNOoJ((!R!?^oaqF536 zKDNpS2jjaWe2Tn8l!Br&dzZd)!w zEjA)(+egs4t(PzAI)j!f6oA@vTk1)g`;bcoAr)#U>LJv))OWL(6G}7-e}Xc%S@s0z zI*@?nnhnXrO_P zXJa)>tMCddIm7{euCq9#zl8Kj$w0KcsjwXGE!W6nkfYiLsNHycM1OJn0x{U=pqCBi z^WrXIVuBVc{z76DD|BIkKh;I-Y$Xc4Q09aDi!Ne%w<(BA;p!pGor)Z&wQj-`Z52_w zQImNhTE85z^H4JKzu^fa;8e#ewJy-AHs;Ufh<0Y;4LM>j<7~iw!{y&qe9zbPG)k4n zPxlkEgIn7GLR~J8>o4A6QezN*ra$->?P*W|(iA*|7P}d>ps<5>mG>cC%@edEF0UD2 z`$21ZiaHqbEK_%q) zg}Ck*Ag;G&pg+hH5l3x%lx7k><|CL)1qdYGZ?rhjL`OKI`RdVPQZ#l*b%&q!`?m{vCp$tPk<(_lp&M=@Kon|3%rk*PN#lgXx&e8~O3^B0u7 z39FqF>Ht!wrgm{4X$lC`$EjaZ&!)Db{!9)Qjuhxr2H$#7_bI~ugRFi5I&}d)lJ8SjI4kh%m0}WKG*%2YErzneA+CGIijPUwIwZRa z!Ae)ZiK0`?-GvXgBEUBzrjA}Xo;%w#dg2UjofR^nYW>Fsf2&4Mhg4<4+ZeX{OtNMs%p_c%k@r zu?tzA-YseEny?(LMXsc%-WN|f#V&QX0EvtU-3wJ}TcB{`(e&4CiT}z=+w;v}6f43b z*ELpubgmeg@NewRPs|kuneGBvyM?;q=81P2$KHX0t4;~6aW#5)%PTx~c!?5@x!ayO zUe9QGu*JYXcZy-$QZ7cQu9}7R_&;^k_S%JfGeo`{717Gu+6Q|le{8N8nsO&1{yRfi zD#Sj<4S-qZ8d)J)#PEB)et0j|SN+hVaqR*zLiNV1YrT==(!7yuawl(EAZ8lx14>VG zA6Z-qdt<9*G1>Yca^VOzx%SE8d2uw^`yt9f6}0%d3G#+(F#@IlJ!S-vc`hOIl z$(ma0YIY@0Y2D5lVe*5hsfHd+Sl2gh3aR^VEX=c3id{_)0c9!71atkjTf#HL&9jbo{7FfQ}!K* zbeOMSCno5=AGC(ouM;y^EI++YOfhZ;f#$hP>%}vcIcdQd*CaCXC(u4=^5V+xA(vZtbs;l9OAznSA zUDv%(EhA7jU!iIHqrl2-fiH=`9iK9I|CETw^5cql*!T>T-QjxpesQq{HZ_B{ib(2kpEh9ZNwUI2^u~x_SiH z8B3`RwJkZ~7t88HgJy~r+PlJ;h(ZMGmueUH*Jc8Sqp6H)B8 zLh(CyiHW9Hf!D}z*Ymr?jjg%oJuA+DZI#{6ih12%L4gv{vMN>VC(mIXaq`ZesNrBk zhmhdAK>NhL#>0TN&{eTd%x_KpgTIM6>;eAg-^7ej`%yeiXmaN`%$tua4`T`^*qUF! zEKB8qMRT!;?EC<)+AntT#ZMPK5)LN+i(hzL9PVn~FV6Jb-B&G(wVvfOUlOy~K7Q9r zVkYQ$@FlTVG-=YVe{t)}V$x`Bb9*Y9fc%uE{%~6arD)I2^=K#9FEPk}!_LL0>nvTiA&klD%N?Hs+p>7ynFh6A^`ywb4{;{cww zxUKLM2#@Lp3NdH72FhbB&m~=YKe}7L*O56{)s`ro@R3CMLwIQV!@Z9&sgtVh*c;uvh zB+E{J>S-f7jEvxh2-n*8#hZ+6<&5SR8pIm*7@yrJc2A`9Fd~gZOFLrn7aGN+?qo&s zYQiPo(&8CKC-fq)ES0Oi!~H*o-OT;G*T-T9Uvv!_!F*Tg$1tG_yn_s1Q&R4PmzUu^ zg*higy#IRZ30TpPRG;(bPl)%Y(Jc^iO#SKDCrIv71aw4J$IF_;G~<_$vE9X+#BNM@ z*{s-KN^|{qQcRW-yMf8+uU2rxI^y=k?*{YZ-*UfNzv{4>M4q@4#ybB>!M7h}`PW!f z1o)zq-I2IQ!u1jUt!A};wblZ>D^7p4zOvv*f_JBrm;2XRIXe!Y?s%z_7^;XTLg6h} zUI=eRM>|sHv09&6|621S21n`^fqv7r2sOF<>_s7-pE)gBST0}HouzfG$@P-<{_82a zJkxGyC;Y3)Rbt+XSDH`x1Uu3<8*BX?N%K2^Jq9^Cv_m#rM?y!IvpzydRK{khgnHvaCE8-;Efwts80#J>YZ$;v+_fBSe?#&4YE>TvwwMD&2WD7XBOw5sUsc; zZ)}JW{NB$+-$1(~t5wO}orK6}N6hlT6@iOc%}A9Kd^r+6?4S|LZdJugEZy$ttZ#)D zGK&pM#A$}bjNZ=evTQW3ya+?t{4)>;qxq&YVvYKVJMF9~pU0jRds=_Po~Uk~@>yra z;p%m^$M~+Z;zBq^wtpeUThF6^{R6>weAE|WCO;4@#r1Xv@Gh3OEHf9!ppxD^Z21J#UlSyIR%VIm$c;(yvtW&PIejArL8eK#H2HM zZTG_u`F)L=&{@-O{NAs`a6kG^W3{seKV-xo{|XlM7l4Y}s_LQMFQOwr?1S*30<~Ks zBOIsPHkd!b9cu?}Z^CCFAbiqRx27Py^6(pSMb_>>`k;@q06xSDFGv=a$;=43a03~w zM)y;L?*cj9W*~)gIJ*AEcW+`by!vY~$kz|h--&>r_oM5cuf-b~o9X)c8&PE4zY4Q& z3vNj5btvU{*obw$rvjrsjf#0R>Y=EH!#zuQycuIo{36! z{q(I^!Mcr)Wp4$KxM7^x@S%yp!-wXm@loQ3_C9&h;z!Ybkp3twdwi&PG}H*`qfz4c zSpML7vAgL-w8OK?rJ@kuQRqtuX7C@*i|M}igP>y!%)sFm_XYUU!hnB>kFx}f^YmBC z92SS~9;-J7`PTYYhY>2b_mC`Imt%&14NjTq&*AR~*b}IWK)$t>0-QV1U#+F|2KqL` z{L>IR9kzM!Zwe`^&W3kz1Uo_;p_>Iq*k;iY?ughdKv86^2r(!MVi5jDLk#N6`FHRF zGJFF9{@oO%@W&9NAcsNj_uq+~f?_s{dt!Ivhy{Pktr+1}voO)8z|IGK59@=$eBuvc zJhBUL#4oc#v`lzJa@cEq@b!LP;4c{iz2Sx#97#an(r_`9pZ{L;_y1b$x)56|r6W-V zC22zl#+LCAgBgCDIhGG(LZW|iO4KSZf6D)(lFe&DG!r7>XWpAPTiX} zj%f?o46?|Vpkqvyf5bj=1;JP7RX<^hCKdn0)Hgh-S1UXTagA$=+eqP&j3xtHOWrv`b=V!C>Ttm&7p> zI%vD5Zrj{K=-gE$+0{e@MnVui2P5c-`jEDCgycP8q0fN~ z?v14bQ6g>PNqZm|1Y`jL&5J}#NeFCM!-y%GFpuY>eiS>Gn=k{zlx)V-3QSRW@>jzX zMkaN7N72VCt|qA4r{%1T0+8$rblkS0VrS8On2Es4+q&R$HV1Bf{QjdD?HdhPQ{jSC zwZiR}#VlbU<9U}w<^M7E{BT)h%({>Lj^sXZg& zM}`>Q@x}BzR$-(~e8=B}>5!-~M)94@LB6AUi0}6BgNOHo0b>l}`vxPV-#3b5d|ao0 z5@jDY-!R?xOXxp0iS)YAV~@hhxw#-(d+fIDGGB4!-{`|ANAXpiCfK>(0cR zqNRBDPuI`Uptu#CUJgY>3-22%rC7B=s+5<;N`v9O_h(|IxO`F-560!+QD1aUIHc4D zX7#=Y7E$-rUmHMltf*ZMq4TL8JT6YU91xF`^4!v#O2<<9Z#+I;inEao$+=&~im&i7 zkejV_2yOhPUO`=m4l{{2QU8l?jhB*avr#`f)K%jPZ-CsoOyKD@4Jj^^=Q-)32;9MT z7CB3*;L8EzxP6PD@7KVmo|ZY)`y|xNbl9dX z)5$Tikqo^$O%b39n}aV%kfQR)Vl@Bw1I?C!B{W%~c}fn}qw6aa7E-Wu0fJNn6hq#@ z6#8LCl01>0OpxLS{e|k*A+Tb7%?%>)kqk(^I;g5}X*oC=O*!z#A$LNIbUwf?MVKhM zX7iivl08i45NH+$G7aDx;3cdul3QzOy5bQ6lVNF!X$VmBiE_mxO0vZ~8uhy@?YzIE zBzD3#(P7_-&Spj+&yfJ!!On^zc`1FN)BBZ((fqY6Y2@(B$QJGEHde_nMS}PEDVBMt zLZO$8JQ``iG>K+z>iZ;PkVhSZwRpHy4W}4WPx6Xv>2r8XC9kveidZ)skwzgH4*&*> zv6vo*AP)g<%D8(y5et;o{)T6hFSi8 zFKM+Y86}$S%I_`N7~91ieWb*$)YoVj+Dk^DBl~Ln;;{T2I@&hKZMAW*mb>~$_Q-ii zYZo?~72kYlc`tfCzt~6WXj%ZJInl0!zS0iSuSE!`kk3CHAl(cxEhAS-%ASriZbmQ@ zK!yN2lZzlC7S4eW9pjJ*O9Koa>gCm4d}*%K6+-%+Tq(Uy3lFA44cKYL!21@b9 z#ZXe>S~yTT$6)C+Yp^tsRq#g#OX)(TkstjSK1X+Yu;iOXREWzn*rt_8?^#UYKa8O6 zUM$5t1P}4hAyQwq)HPv<^qOcT3(dg7d>%bQngEuYKSCNST$1>~5mN62>Li+{Xg)C6fl>a{xZ*RS~2ky58QBr;i$%u6DpU#U|P+5sWY zZsXW={Y4u@$*Ay83m}UH2*|t9X<)qTG)n4ZtOc5nx^5mNy&?Mi4f3IDPULChq*=yw zQ1Sy`KTbLqMw7K|SnCuTY&)iQxNggnavAH$kBpa!;Syix2{7#K<#`jNnt1eSw;A4d zf)Du5Q{UH_iIq-bpgO_NOpxH;o#1~?klyG|L3}up9f1JX=JF^6qY+T}9)qc|2*x3( z#dtLyQ}g+s6Qx10Kpi+q3Jdf07I``ru10H3;H8tK&+CRG^E?C-P%^2i64rjj_)K0= zg{f);D-qm|;7$N;5x;GB4?Zdg9z?Je!2tv>BX||Tn+V=U@GgS)5nMq)`&GZtfWc6A z9|R!?!V%aJbU=`epc{hj2>KxChaeZh3*FPSx;ro1F@<;2WxOO#7oBCi>MQ3U=!;;mg!g;!`lF0j4Q0Fv zC*!p)xe~!*1b9nC#!C+}UQm$nfL_MKXBkh3Wjw`{@#sx{1Oc8&$#_^JA3*RD0z3hb zahsmDKxw~G#?56JcW!Txaeq+8Z3G#o^D@pdWgNoQ0h2m5Mr;`a8igUaTk^vc$vzc( zq1&&h5?;V=`S&nrvO`B1d%(rq0fi1nL3xS8QRZA+C_CYO-p3&uz1h%(Vw>BipSsID zK&}UBSWvyqHTOm-RrHHsL7S#sF~HB2uC6<$NkRT~$;@=p-_|U`FMP|e*$BVzF|d$` zAI*@uEEw3)hJR|v@AmlB@eQ^P$5_TCA45p=rZlDVsVIA6Vz+%`k0lN|7B%>i zrH6+e{GRboZZ^U%(6iW@&6t8(5kDf1hjI@IGOJu~&6QS5e8UpSV#{rYmqGh9_aJVo z;v4zw`4DI>yXxjkA6eKXp0`li!7jNjEtGEd?KVze)7VM>*k+MZXM)3>oWj_2sF>0$Qi?RE6zU$c>4uHjm*HPe0UWH{;S&6lACPXYa5b%z PJ{HW2`$i60nkM}Zo)F|# delta 31740 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%0DDKCzDUi_oesWP zupUopJh<4beiO3)Bu$zDOrM4H|Jr|`08Uvf<9MdLd$td*)9vK9cBgY`S`gzu^ReN3yU@saXwcK!uORSrtARJ%qCMyq7<5$eQypfz zf=o2oGuEjJ`;^77W-(OdT}WK9YpI*Q>Z2pFwtJEDS+3Y`I?8^@1!_SQ=McMqXeWTB z^3VcWZm|e_S7sTQo32b~B0T>)4o7Uw_ohRZLHo57w)hncNNMGgdMA>R0t@SJPMh%>S=^wY{ET=Zw{_ zg`ocW_AJt`J(FC`?Eud2sq)V&)B(2jgp}+Vyh06D<2U$x^LjLG_RUKg)UKv)c4gnp zvic?q==V)F&eig@2DOXr2BI-jo^4QTxx=6-)i+ubpo4y z3)i5R{h=0!WBgHr)(z~keoB7l1*)Dc-(RQZn;x8E`Eup|iw7Uamcbui{0`mSAC=?U z3heJg7T65U@{y}3zWfJAhGk$QYkUd4T8*@AB1_kJGOkvywO?=}dd_rh!aox=!u;gN z@@+ZDoX_6H+%pz~lk5MwLCrB;ztA`C{;yo0E$Hu2zb)BITKBCZUf&Gm{OBo+^=}Ny z9`&ECYF!wN-l#^}Zzeu_;yf2`RL82Bo8ea+u??^{M+F(NKGn3X9A2X)nFFNo+yTNO z`v(Y{aEna8NzJxzA#%UQ$CV zWO?vzb-(RFs_W~yal5)Sm~xtdJJsyYC1A}v3ZI@2X=WqXR=*Q6|Awbq<@BBE(1r>O z{M~59*5&0NcZ&azr2w^jOT8WH=80W5P~;Bf)g8wS=EpsaHJ#4g$oo2;5JJra1^tT1 z-M7Ws`ObHW?DB|O9@0o6ZlV0QJ4OzVQ#yG5@rb%uOXYQ1#5A%&+ZEcaa?)P4knV0@ zL4Pb+V7Am=`Q%>O&OQz+#(O^6tG?2F$oUuyF-`9AsF`;71>NtvV6U1XfA*+3{n#FC ziuP8qkl##ku%4{8sjR%<2{k3Y81?>f(kurh!L#g75~rWyJr6vgZf-5d$Y<0!lzG|q zj9T3L35b>jn^{bVGUnu)vbe)2t-?svGH0JU(Dn~P%l4G*Q!i{ye*Zt!9`>6^E=M%0 zncEHI_d-y=`0UE*C|iiyeRAOQYD!y!-XvYmtKCEP6Qgkn)JcEIqkGjco-dzQFKxan zZ(0^)9+uM%QqSy=YYwViNV`W5s{K+}{&u}-%PVU3cz-5)27&}TCGQF7W(cHe&$y4m zI4;!g!qaYmJ#yhIY7g5B^t{k>_bciS)jaF4>Nn?*nqkoo_R*h{>kp{~wwI{g-5$>& z^%FJJ7eE}E%sQmTHBj!B$#76Ht=cwxuzeYcM}R-fanRs={{jfiHfM9UbJ9X*n0_}x zH1Gyi)i@WYAr9btg?{UVUg<|_IQ7DeZjw`us6FkklK4*~c&<63eyy251NAhlISm@_ zldIoX`}c#~UX3PG7X?E0QPw2J9O;h(-3|$p5FmIp%sTCpr`}hy$Ls|qb{$rO+d~)H zM)I>?ZuAS?QlbFnk9ltW74*?f^fLf)VLE(?!LbC-@(0MK|3Cn>LdBU7wXOVd{Km*)_N@VF37zH zz-MFpQ!n|pPpF{Sv*Am%NQipRbH~*Qnml}zK4%>;L#Q#===!!GFntm30 zW!Q>Pc|nB|FQ1$wVr5XO5=Yle77XD3GnM3w`C>)Tim(-dw+A>g?@;(})*VXSV7a+b z=^%%FOdmTAmRaAb1&+Z!+O{2}KmOJE%Y47lPE8jPdUWc?wU^#rC8oMP*jSI@9pJZ4WQ8!9P z7OM0mW`RY<2>E7#NK6hkNzlhn9i5I=)t`Q+rY1(LidZV@#jd)0H2MNrL^o2EkPr@bh zPpjQ?arMU=dLfta6(KYKkD)+|n�}e_BmV;rz2-0lxPC19%*jyH2a|X}{w68sh8? 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 Date: Sat, 24 Jan 2026 23:56:21 +0700 Subject: [PATCH 5/5] Fixes --- __pycache__/server.cpython-312.pyc | Bin 299834 -> 308458 bytes server.py | 144 +++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index 8405405d574aa3504dc8bce5440dcbb2ad6230c6..d4011c16249b49269cfc85c4863a6b39ed8e0c89 100644 GIT binary patch delta 21390 zcmch931C!3wrE$?y*Hh7I!Pzp*+V)BSvq886Oet8Ey92(2x91@JCGQXaMP?44Vw!B zvRtKbR5nFHWKl=2E8;GWA|Z@{76sgw5rZhW`_8FO(%k`l^WOaTndeE}Q)jPJb?elv ze$PA@{pAj zTf($y(aW^kq8;Yvm&?155B^PkNwdeqnfC7@zGqjs78RZv^MX6c{c-}JTy^duSGL3HJ#TKs}nF^^WcC7uTdkMWki~l66?>`e%TDZ=-+Y zUG4hV1k!>$LzDo~$2yc8ht?sXZes_oU1GS)8zAu=A@Mz}H8z!i6>(|i69K)4MeqCm zUh;t!9-m1By|y>rqS@m@v^8-N=2HP&N3^2C_;A{U{T%@EGm1w&YBFtw4$dCD~{#M%* zf0>q$7-2pa(0N?g``tyNzSm|X7HV^nx@&THq;@z_hUnV0?-Q-&9|E}F(-wD!fc%J} z#`k5T^w6 zo<>7R$ojrXad(TUEdhZsfZ#(UC=Ly<)aY6h@D%F$FyKi{c#sf=lPgnlt#yaMlL<1k z-5(=7hX=_22+gyZ+hw@IJ5o?g>V!*9YLF43ZA{IKiEQT!;DUfyqB=#;qQ%>@NwoHp zDG`!GLMMfZT7GyWh9oAyqEFGHBx}25=(WLru>l&uw|o=)x5)KjyDdN`q-8T``K(Fp z6l;5coG;KbZ9rEGbv22+=^Uo?0OBu^I3}ZAX}iil;OrW}`IX42OszG&uQob8O6!{u zsdX<*&`L8bT0}&)_H=rb*%3hhHEJR`T2y)*$%Vuzz}R-v;=5+r@;a-Bo_r+1K`RK5 z@eM2*Nw&gEkQV`YrWVpQ%3K^k--iM>w0itJMK+gr?@*td)dozvZ!2zm%5S9(Ogk?k9 zEgKd<{-Yoteo1ne31ozJI6H~_ghOJxl+cmjPg+Wq`@4?pRlti;0UCZ5p}mxbk(fkj zBVeut@?&sD2k`%bv9tXle+u%quMVhtOaSk%=$-a!PN?>|!$O8?_M9|x$6f$vM`+`6 zQq3g+9KVTpTzScOv<>XI0P^3_Ic<)kPsRk{#`hhjpc@nYlxxXUg9zePmk>P(17Sf4 zfsS0QwdQ7%YqV321Ty&&rXS8tl5wg}(WZ9GCf90pxhZ6-wxwH!XsTaU== zg6v?z#p#!@V=87yG>E)Dh(SUIQi=++yYlRjxF&a2;SA!C#S|{n^78tUa_ukqg~X*T z&d(U)7AszbfHMW0C154Ez7~uQ3EC#&EUF5Ut6;dv4FPhiQSQjuLdyS4I{H}?Bs~N( z4Wu6^=#fF87o-LaNL>)M5q;Fg7UmIEcvugixA%9wAeM8VDSzf%1Ss$X2@Dm^&DBZ@ zyS0g&hod=YKA7?EO2v>Z2$C8moSLisR^)REr3Gf$q9AIM7`VTJ4LRC@B8#viK@J6} z4O(ka8o5!6>Yg#u=iN;r=#9a{2$YNA6>{-lVz`$C=?WMAE)CYjU_&4@EkjLLE)N*k z|Cv53=({;cpIIyUUA!0^ae?`=xzetqfv}!eEEo=3a*^THR%t<|_AkzX@7vPpg)m5=T~RV|wP3JG6&; zrNABpXMn(D+yMqxxwQ@sTyoY1X^0Vnzgk<|%aMxIfdjuTh|((DTYpJPEr`-4ED)3y zp9Obz$Te!Gdgn{ddvAUDbZm(q2qH;#*K3QDVsW3cUHePl%*F?U zx>ErE2C~B6hr|Tf;XVZIo#1fzq7cxEBmMAl8D1(~LJhwE0p?&3&n}>b`VR1q2^`*6KQvNPBDNpz} zDIhUjq{@>xu3f9v5_ft~-1Q(i8SeVE{M6gTrj=Z7;q(xR=?SJi)jnGx^hyOT%1k}U3U*|pPtnoxV&%bbI^LeeHH>$Ht5b)dmvm(t0rjnG+R=~DaCNZ z8bdjq3I9!xF&J(%G#Io!{bRMoUmCSB{mqTJHOVtk@(bGtNXl)~cK~GN-eb6rw%bPx zZ9Okugn8*=TJV)tP4=x4fzWLRO$|sdbiTZe!-Ag?;B12#;@-~LdfSlEuJLtEzi>;?clFgUa*VVc+^;8<|$1KmXK=)E$x)mSVLSgpcH=i2@)$WAoU4^ z7tNsg#zmkTIzo8y3>vGQIwJF>pHrIzs8HUNKw_yHEB}CRTKH6sVt6%XVWB<5CvQo;35UoDyfNW zEpE!D$?e_IfSgd--VH5V5O)l8&&gwL+}IM_5pBY)KEL_aub6Ng725cmJtWqm5r2BD zC9A#yKb$0<^ykhnl1ch&O;7gL7Vikgmt-nOHK9&z7v1kK8_nWLlJ&h8sf~ zl;MWy??bv88$x)I46a74V4<|Z$k$DRSnO*gp>nJ%4xX13P#ZDw)2~n)i)}D!mMvBc zrGlZvZU`f?&AAT(H}N%U!-vyZ$fHd-DLfPn;UF?aL4SMXe666a!Iv{@qbvU2aKE3c zPD+Lb))3+uCK3(+RJVW`ZfKv#?X&?Di9iu@F@-DXUg;{sZZ@Adk{4|bv%|lUZjZfu zuBy6gJa%8UH)hw{1M=NItFqoxt1h%x)|6Mzbye2PwAarqt1nk8>%?@hJ8N8aS8a_` zJQWC`6WyMi%fXd;Z$!IEzGoHZ zyflOl?6z$_uJ?d%c#b zud%S$Ms+Q4tLspNxva9f8bVT9zOcO7?Twi2teNYqF0FGeoDEiZjqOILmjV0EaN&@8 zJuDzT572mFnJ`Gjl|^+Sa3esKY6Z4pfvz0>6aV;SlZm;p0NP_=3FENhPB%j;lLq24 z;7b)&2UyfxWCP)Z7*<6}hJ_S|d?ypZQbC|$oVpj_PO6TFCoie5N2J}ZPJut_U*KU; z?o79*)Rz#Y0ohPr(7(F2+*v)aeqcfWneH0*f;x3zGz9mY9auj%{IJ+?xWL&oe#RSV zJLc@`(o{J3Na2VBLz)Xm0Pe?g5)dtou@PP{nZ*7td{C>wwqV;`|6|+RO>acoW6<_4 z9k%CQdvL%3=Z@kX*Ee*oL-Lvf)3Dwf+CISb-c(Pm2Zlbd zKR6?kd<5+rrA~7|zBGJ#l=vcVeFgsCwbTR&s#5o~QU@Wduk(5N~w3ShWF ztww;^+&Ot(LYzpRj%-zDBHJtkn2x?|z|>MFqjRC^9KnNKa{=MC1o=`8%YZr$D7;;y zx@u>QIuB{g9qwSim%$@n@*A;U9rp+Mqc#HDQN>`WuY!Xbkua@RgDsAaS~u$6>zb{( zE!N^DYw-!AwZ)j+WK7-?{!rxB$R=aqIR>whiD{&3V|1(0kdSm(vYm)bzKtD=w5(aM zV!_e)oaRVJOJq?~WYN*$AuYwjn~I0O!QL?)G&L1p)f_pgC30$0u!^d853HJ z_9mmf)exN?b}T&V&WyF?ykcF2)~%HR=2Up}+HtKiv|1H|IdP}7vFGN#f9t!ipfv<= zjiBC~v~gHVa&c2~@sZ@2`^sB-PiX2r;qau%Et4vm;QxA8w1y&gn89q{64R2_zbUPM zs|hjT26M{B@|G?|Oh(@P#J*~m7B-yPl(U(gg^a5TQ?fU7kX3G4=Q@}oUBPHIUhJd#rQ)TGul z#HI&2nE{ydhUidJt89o$X)Q2VVz_ag@!t66sPvX7M^jXz`s}{6GrZ+{Vw?t+)MP@@vG%LA#d+hA7s~5s#-+W(|y`l5cU z&*hg4_OX)je;nguws?N+x@*rSn7f31Z!m-Z%n+3Tu}SQ9%$9zlTS1dpSDOoRoz2~x zC$e*!aII?2y|y{~+Owf@xYBBnW#z|um|BLV~u^=c~wfjP?xSXD5?> zWSO2fh0Gz!T`&(5L-hNvB^M-e3%}=jGQ_e1aO&+iucZ-_oHNyx{Ojw<4AW{v-+^Ea zpHfQl$$EZEDd|Nu%2Q(`8$VJ?tjfL6KR87HS1Bn%c_}V3Ot}qtR`LoL8PG342x48h z*4&M)NL+B%L(3rttU$kF|L+Ji{=SRk$eVy-mLBFNyBOKbpPWTJWE<~RN%G?EM=1{= z$VBiu$a93Kn|Vzo86!UosN41DE6LZicq?{q2Cyh}q1&n2hmW{wn8U1YM|2)qQHkI| ze0&JOHvV2U>?=2OGMfyS_W|l*eavj~I_-@@)K8K6QpzKJBJ(>${)B#Pro6b4Br84+ zURg)3k)HrL`}J4q$S77DEXf;ISzp?=G4r`(@kzxOzt7MrY(0yXi1!2zyzf1M+a{lN zPh;;h2*mp!Pz9fJ2sYkpJfu*59tgbp%N~-(%sLW31;7FN_}Td+hCbsnJW&X{NDh!c z%lj`NqvaQY>j(Yz1tf_ouR=T4sP9@tKB8nhUvndwLSLtR-F6zc;U*HHyb1kjM%{W7 z*(s3+`TnJ(AbJPjykQlUHO`vyN@sPw`W*jlDalgAfNbZD%SZ|zxr}5*<5)Yw1Cr%I z>`XVI0QC@p=lCPbNU?Gh+5?UHN6W}fvV0sKX6O|w$O)OW@L_96ANeGpZq^sCA$!Bf zdwS8|$VY)KOZ9h zBqW0mIYHvW{97$Pp_Rtdqefp@dEhHDmOq$BV>fgmuSP))BR5FHp|GBQ9=3i8Z~6y~ z&oIC_pNbU+o#T0`Pvd<3W)(ZR-kLIB7LpkejD^-35hkTXxX%<&%_A+ZpaVDRrP~m z?DP+*1M%9vGs%L*p#7z!UQ0TmC@p-L3hbON-ogo~*p+k;EhAKOaRp}tae zsCH;wGLL$kMDoTFSgQ`@TOF*Zy-W?|UGDT95m<~c)}al?wt6+JA(Wqfh*`rusqJ<* zgz*XQkQ6!5l?2VGNieC8&!nR8Uk86h?6LARLvPbt#74q=1!|axI0Y<58~-jzh1&P` ze5n4HrjnSBXhH_n=CN2`kD3Q|wT}af0j{&aZE+=cDp~pI!6c82;*ZLbjiod&el`+@ z^!ErVH_8p6o-5m}cXfeERlc}V4WLK`iqMNGT=sjFwpuoA(UOi@)@iS=bC*|ERF>Q4 z*1OeKAoY9EhpvvGg8k@s4IE6&oIT93P4 ztwd>Jk6(qY8^E6bxP0`NUg~TRdU4g2gFAkV$EE7U_L>VzuC;jNs@9@qb(H5#Ch`AK zC8%OW`yZIIF4RzHP(40#;Kib-P&OO9p~96K=WMsAhD5oe&PS>Rl%I}xg`fTEN$2^Z7~M2n)% zg)J=V)W0IYGEQv}Ed)0rxCsECvzx`H+b%hfY9uHgjqk0aj92bw!?U0``jfgXq{*25 ze^ax?TvW3Grmbd!)4^enhkq{Yc|9L)oh3etl986xMoAl zpVn+h@ZZ;LNcHD68&dpf&4vUQ*KCLluGtXN6^h9A;_+-IV9pz2+N-$6qKnG7fGTce zlQHA}wt{QyR9ydmtl+*&H(2AQto88u>*j0ykHsf%j=Veaa9qv~vZL$Xf}I729eoem z`W;Ki4X*MoQRJCJu*eJPDDoy`O~}!A>?9LqlmE4IL6`TEZp37P*?K3=R!aG!XUHgh z**-39cDR1iYvhVh5Us!V4!MSO>AZf57o|AJ!qt#3Ya)f9 z|M4brfLa-!dxQ+2S&aYv2zhvng4HI8~1~ ztnJBN<5Kbc&G$Bw?xx;AI1=qNFn;+Cr70zZ446p?(M?j>hT z{eWpFGKIA{SNa}#7@gy$Pe>&h#OHnjb;kwX=ToRP^YrUKg=&)w=5KvQE|W`u_if$y zIk{B?dfnHg7f`T|JUO{2Men(2k1%1r-q@0-k0-Yr$(3vYnf4Kh3 zd2*c~oBAv10c3alN^*!Y5y)1W^yhvfT8MHQ-va&lO%|HKYZO`mivFq48$(VYeh}k( zdeCIc0N_x&;@hMe-yv3}6|YF&x@gBlr&T^jW@(l)jAE@-tY>ww#EY^Ar9ha@IkfI z&eN^bCRafBdA+}tj$_2BuZ*KBCC6lBpMn7FHmFmvwG?f=AHj9_i2EURIv-jt;kKGBU7E&=|3EG;Uik6x)IF4gf37jp*@SqzslXr@vPd~?F}!VGGZfwH;>E#k9s=s@D)59QDSiVFR! z!}Tw7=;Tma4Js7d%V9VwSD_=d`Ze9*gOiW9nAGu2J!tP(F;Ih${G!oPdeW<-gcEbH zzXF9!;y3i9`SLtK?9uP*Nf%kkc^*HSUK{lhD*6<`CkQ^~OGeWyDlxuuH0@~PC*GVNb?%69X+r9B;jyrglwhH`#ZtbV)gUMj1`X68z$9$S*`zA3~}A ziL;&GUq*Y$cti6y{rxifg3$z*G~3d6A%Exwx|^)!S69=~bbJKgQcYDzf|%K~_mErA zC|P|BnYax3#1I9GQ6J}@ z&8AmG2|sT|A~7Pj@c}io2U*4E)X+^u9Pv+~Hs+aK=QRR=?|q(G>Z6E@bk;bl7uLhs zo$9WiTkTQr!{w@%@-lR0N9c=c>Fo;H$>n);xRQ^O9^>QZ(Sm-@Vq1)=j;$>i_=x}< zMqdzLLF}st0>X)pk8yn-$Ug|}TO#yt=g|e0#GTk7Rtd2{ypE4;%9h_o2ih=X>Nt%2 zI@GfW9~*hgZS+<=q?)mUUPhMjjVtI6%5v?7&%rmb`yU88JBYZq z5gbC`Mc@~G3!lDV*E3hsv`C2h$b0Ea;91yx zv|n-(^enQD78BpD)>c>J$7pBy4gKuuaX#Zd`bx3iY8)4@35k#bXRWY8%#Pux@&g1X z`PG{sa@+apO|(yrsrJRSZ1(AB6&2+B}rhWbZ%}Eyv2Ii$V3YM># zkU{mOun)lx+HEdlvUKf-B)-)}Okj~V2XQz%;sSG}^V~z8ANbnsG)w*w+AaFC+v(jB zsJ{AP+CK-^@kL=HR25D*Cc*diMP^_7O0dB>)9oPYY80@TKm9PxQhq>XEBGf5Q-^N` zuLOi6S}Yn-?<1jaaYnMA`KVnqW%#ey-h%1&JGMFw{Z#{yw`X~$P>DRHDDD^J`HFXm zkz)A^yJ(#JD`0=s-`hpgWW^vE3$}looU;Qk#NU|CMviQyy=+Uqcpwf`4fFB!5uF&5;KzD?Y6MWtqbe(L5 z&OLhgL3~gD&TWV20U5rn8&2!qL-aGM7%af&64LeXBeasxI18WOOedJ4fl5UXW8uxs zbPykVnkMRbN9krNi*K^r`!39{K0K@iHVcLN!WJ4uXm<-=d5qp->I2p->T)IQfM#)v z7tVBh9H%9;kA*KePH!c49_pnt=|J(A%GbdIDdazT=>|I7!q>e=s~Y1_{TYmWJc1N_ z6q~m__}B#>QxT*gNJo%?pb+EU6b2=Wo&MFLetATAHs5Qm@(#-o-P z^hGD=Jc4(U<}}3fun*`-Qz6nID6#PGKA=5mp`^X-iPwjoq}LHT$-=jsq7|m@NQGd6 zgUf4JQA3GV<3vmdhS@^Jz>CFnZ z7g@RUQ)<;e{1{dwWiVi>EV}7asd^ODV5O zW7+ZykoLO%w=_0bGL@p;B26#wPcqp;GL4sJu?ZHzAH@1%7R!^%f&6Rz+bp)jNIv5C z6tXVlGyY5=t5MFNv)}N7A~vM=ci0x&V&7X9A8$n<-m$s9Pru^S`3bbph4};tu)T;4 zY7BVmBF8TX#EbV4q!M2wwsqv6NECmBMgEDb6UieIU>#}@NhlHpZSd9LDG5g&F_0#(U*wm;Bz^T1a z$p;9k_{f3mGP^%Vb8v7(js~UYrh%-$RE4~K(8?2hz_oB`^~^w)YN|)9j25dreh|Ad z>%S8QNni%WD+jRwM&G%AJ>}uUq(pvZ5Q|h6g1oC@bT*hB41>&ndkpIl9c(r1XQuM# zvF!FMeQA!m#oYqmHyJcg%>KUE68p?#B=MVn09~Jlc8eJy?g=)-=kt*yZbJ!+wTX1c znWQd8umr&}-mQcsh2D(qTL6f|eC{q`R{1t~yjQ=egsm~9R%5?N+V@cBY<#RiP>Z0B z=S^WFl+Uos&F4>nrR*HGEBMYS%#rqod0F7w@%_xtO<`GGJt!&-ox@KlzQhx^75pBG z0g6wZi7E>g0P$aPkk4RlHvazUBq$%Di@og|| zzVS>SDW(XXCHT@;jLLj8VgUjLJLVzqCoq0mQdR5~hDnfr^)^0X9F5#KY-wVy||RQS--dOL0nGQh^D=ak1g2h2tM3b`4n9jFHj-iej$Y) zE@K0rq=+bI3qsmr#ho4&7Z=l+!KaZwTh4A(?gn#;tPhQHF$HR^(N)Z0|05g5wHd+J zSFyaP?Wi>mEy+c27e548vVs3x#Rm8Dk5zz$$RKt@R`qWfqfT!4tx5|~HzEP&r~b;) z`3pC&V&y(S++fwu-@vk&X)B5nA5rh+IknL1GlmzfhOI(FEenqo3jFJ;uydn?mr7#d zT<&VO$6X57i0h$&_c1nu@Q3v`YT3K2oA6%T3<(|q*u!l`wZiJQkRPmPy&QJ}vm-9x z9*-{`=)ic4%;z|K8{mD~zrTp}@r{GHKXlPJ^e*!2@mbt41TpG^XrRA8zz*dB>=MRIk#|D-7kxkj z3uUIqI>Nq#PiSP9`NE#>54%kW^@m-U^#FgX5!QxXAgHg6|JKMBkS%=vVwMr{G|GDh z!LtB(tDD8@`xmprY=m(2IqV4@1VMO3z+^D`#=|ek@8`3;c{%Im8=k~~3sHYMJmKN! zd*Jj*wsuzG3zaaNpIF^7ZXqffTyMqs9w+9z=@xhe>;ZujZMyvym~3P>-?55iX8na-Q`bC#)42DI(yXb|R_)u>i{)HgzvL=O-6LoxM>+*xROF)*Y-s zehj$o)UUmRjbbJp-4-+NQU1s}mhVe}f`AkdI{YahEPsxttcNY(-$BrOeE53U{%q0r zuV;N3yc2)8i}h04ws^*^EJYF9IS;>P1DgQXy>@J1SBW%!swYcMW9F5zCSg}Tn zy%gT5P)}fQHnuXcw+vh2_3e|+4?n`Dks&;9H=7mK1fmzlLOw?K=Zu*-ec^rM4I^{I?GfrCXNI?;W3uV&+BXjkV1KxCbt0onEv2X zj3ep5=h;5nyMXe&d4(^@7}qyB+s~#cUg(nJbk}}18Ofh}i5(9+3Ak=?V9@XU)MM-l z{ganjnQUrCA!3X02A_D46_A5`$w9VI@p-i2O?Hj#EhNEPaO&IGI)vaIe#4urIP61c zk3*Sj`KD_E|1fnI>g>*rjAi}NIVU1BmVsev$m(rp(E{S zqw}*zSRY86+-5de{sJW2qu69|Q<72i{`3@C)&nJJvij-fVT^z5k{)7z)g&!!rbB5gnrQ*16pxWrl_^NN% z7vWR}a)v;b`I1()m_m`k&ayI7cg(3C2$u1>vn)~ZA^6&}5Qjbd;8~VJEi(W7EYlS$ zl9lo8-?Gc?qRbY@HOny#Z$?mr{lB4QGwFtN?5@NHjFDf1_=aUgD}Lj9&oNtcBDC=n zh4={!o~7^M@1A1=luW=+iPsapV=E={FMazDY@#wyZ0N)$Pi&6FHc4!4!+?5{YC^eY z1RtX<5!e#r6@{(ed9UBupjd+KPz2&kTESMN%y0jljnD3o4{z!i>=2gjL7!~+7>mFn z-zoJ75hvX7@?HI;0+Mi7rBqdvgPjflUa6`UE+*yTb2kKHA1=0yqFQL%ZfmWl-GQV`&80M#zodIVcl{5m2Pno<$*6*AiS1|p?)6@l(B zDZm(UTT4jK;2#jFMD7l(pX-HGdM#4vhL+^>pDoe@a6-5xT8j65n(W2*M@yGm`@q0@ z!)JMBSC^KHkE9;87dOX9{gr+oWN?B$K1QmDmBnw4__GcvH`#)AlRjQEH>f;SdOd0j;~#5Bx8mmC;&ae61F>nPN9HGii-nmDN|w*3PX zkQiu-rR`^BrIq+K+*4ZVQimeLKM0Zt8e z6-tS*!YcnDiPH;MiQX%eCdpGl_C~#Dk+dL`hRgb%zETJ?iH-U7sJfIN=r6?+CqLd_ z8Z|f^Q5X%cT&IGOjxgU9v@sYZ(dlDCGA96UNIh)T;pcO{FJEQcIY3G=i7AF)Rw7?9 zK>C2V^;rX@=UMV}rWA_3aD&y2_9Q6{=MTL{3#< z1cU%_wB(b5z16Zf1XRRpzLM`BDs^?ZvBQVxkm46FBnut0xM`S_qs#`PTNCxZ!=wkK zEdK=a+S(S?`R=k=wY4{t*1OA9w?{35@bHjP(hTKtl)hA-JxaQaz!}N*(Nammjfi^~ zg9?{e%5P}D2C+our`~|;z4kFujC?E5yr}mXBi+x8?fUpfS4vB})*<&C1oZ$M)EnxY z=d7%DmQ}k|T;v>*+MBN!C%N+dv#Voy^M(v{!U=1+TP;Be%Mh$UP{tF-OFhE{%W_2A zF7xT1uz0SHm*VBsfca5hGhT8~1$!5z@uJC6tT|`?EbzFrprF73-xN{y3&MW?i<%y-fc20 zd_Hga?8#EGtv7axZvz-X-*IO(e|)l3WD}}|OTyoqk#aWwVY0M9Sr6^PBz@i#DMwN^ z!NW~S{DJGF&q%($YPxh4A$k1N_0lZ(-Z{Ed>Oz|MtWs&IZ_%2LYQ*uvkNl@nDFtfx z_!-j6G>!51XGqDZVi_2VzLp@k62Vg(8SVfcM2z zy#1x(?Jo66BuU2>-g#2{u||M+8%Z69;4cU!BESnjY6XHy1bCZ8RT1D-5*05Iuzz?%3egjqUW2E{k0{o7rQUnqL{1~U25kvxLRBdQ_G(Ou96e8%3pcexC zexr&{E%?nuEkS_C^eUdjtJfgFgJ2cUZ&f^rRq^mq#j`vWkH=Iz!cp6QkD519r3Ulqb{?XslH;i&9pl5;x2emJdrb3%{f(Fq%~ zw8FFUAfgi?J{6-j?V0lk_@@3V>_CJ{N0C)k_P zdh9D{P8-skF!XH5C<21aQLPLcAgNWx2HeAqiThS`qSVV;slZO6`uVxi1W9SK3~eUE z_?AUdT++884iFIow!q(0V3Q1gK|pBKIo`NPN`g6d&mt*#!);QA?p-7mgp#v7bh$Kv UoYki-mySh*EVRcD`Kw9#FJWQv{r~^~ delta 16080 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 Y-B+bNa!E71CLQ