refactor(scripts): reorganize scripts/ into seed/ and validate/ subfolders
Move 9 init/seed scripts into scripts/seed/ and 7 validation scripts (+ validators/ subfolder) into scripts/validate/ to reduce clutter in the root scripts/ directory. Update all references across Makefile, CI/CD configs, pre-commit hooks, docs (~40 files), and Python imports. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
85
scripts/seed/backup_database.py
Normal file
85
scripts/seed/backup_database.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# scripts/backup_database.py
|
||||
"""Database backup utility that uses project configuration."""
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def get_database_path():
|
||||
"""Extract database path from DATABASE_URL."""
|
||||
db_url = settings.database_url
|
||||
|
||||
if db_url.startswith("sqlite:///"):
|
||||
# Remove sqlite:/// prefix and handle relative paths
|
||||
db_path = db_url.replace("sqlite:///", "")
|
||||
if db_path.startswith("./"):
|
||||
db_path = db_path[2:] # Remove ./ prefix
|
||||
return db_path
|
||||
# For PostgreSQL or other databases, we can't do file backup
|
||||
print(f"[INFO] Database type: {db_url.split('://')[0]}")
|
||||
print("[ERROR] File backup only supported for SQLite databases")
|
||||
return None
|
||||
|
||||
|
||||
def backup_sqlite_database():
|
||||
"""Backup SQLite database using proper connection."""
|
||||
|
||||
db_path = get_database_path()
|
||||
if not db_path:
|
||||
return False
|
||||
|
||||
if not os.path.exists(db_path):
|
||||
print(f"[INFO] No database file found at: {db_path}")
|
||||
print("[INFO] Nothing to backup")
|
||||
return True
|
||||
|
||||
# Create backup directory
|
||||
backup_dir = Path("backups")
|
||||
backup_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Create timestamped backup
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_path = backup_dir / f"database_backup_{timestamp}.db"
|
||||
|
||||
try:
|
||||
# Use SQLite backup API for better consistency
|
||||
source_conn = sqlite3.connect(db_path)
|
||||
backup_conn = sqlite3.connect(str(backup_path))
|
||||
|
||||
source_conn.backup(backup_conn)
|
||||
|
||||
source_conn.close()
|
||||
backup_conn.close()
|
||||
|
||||
print(f"[SUCCESS] Database backed up to: {backup_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Backup failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def backup_database():
|
||||
"""Main backup function that handles different database types."""
|
||||
|
||||
print("[BACKUP] Starting database backup...")
|
||||
print(f"[INFO] Database URL: {settings.database_url}")
|
||||
|
||||
if settings.database_url.startswith("sqlite"):
|
||||
return backup_sqlite_database()
|
||||
print("[INFO] For PostgreSQL databases, use pg_dump:")
|
||||
print(f"pg_dump {settings.database_url} > backup_$(date +%Y%m%d_%H%M%S).sql")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = backup_database()
|
||||
sys.exit(0 if success else 1)
|
||||
586
scripts/seed/create_default_content_pages.py
Executable file
586
scripts/seed/create_default_content_pages.py
Executable file
@@ -0,0 +1,586 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create Default Platform Content Pages (CMS)
|
||||
|
||||
This script creates platform-level default content pages that all stores inherit.
|
||||
These pages serve as the baseline content for:
|
||||
- About Us
|
||||
- Contact
|
||||
- FAQ
|
||||
- Shipping Policy
|
||||
- Return Policy
|
||||
- Privacy Policy
|
||||
- Terms of Service
|
||||
|
||||
Stores can override any of these pages with their own custom content.
|
||||
|
||||
Prerequisites:
|
||||
- Database migrations must be applied
|
||||
- content_pages table must exist
|
||||
|
||||
Usage:
|
||||
python scripts/create_default_content_pages.py
|
||||
|
||||
# Or with make:
|
||||
make create-cms-defaults
|
||||
"""
|
||||
|
||||
import sys
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
# Register all models with SQLAlchemy so string-based relationships resolve
|
||||
for _mod in [
|
||||
"app.modules.billing.models",
|
||||
"app.modules.inventory.models",
|
||||
"app.modules.cart.models",
|
||||
"app.modules.messaging.models",
|
||||
"app.modules.loyalty.models",
|
||||
"app.modules.catalog.models",
|
||||
"app.modules.customers.models",
|
||||
"app.modules.orders.models",
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.cms.models import ContentPage
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
# ============================================================================
|
||||
# DEFAULT PAGE CONTENT
|
||||
# ============================================================================
|
||||
|
||||
DEFAULT_PAGES = [
|
||||
{
|
||||
"slug": "about",
|
||||
"title": "About Us",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Welcome to Our Platform</h2>
|
||||
<p>We are a multi-store e-commerce platform connecting quality sellers with customers worldwide.</p>
|
||||
|
||||
<h3>Our Mission</h3>
|
||||
<p>To empower independent businesses and artisans by providing them with the tools and platform they need to reach customers globally.</p>
|
||||
|
||||
<h3>What We Offer</h3>
|
||||
<ul>
|
||||
<li>Curated selection of quality products from verified stores</li>
|
||||
<li>Secure payment processing and buyer protection</li>
|
||||
<li>Fast and reliable shipping options</li>
|
||||
<li>Dedicated customer support</li>
|
||||
</ul>
|
||||
|
||||
<h3>Our Values</h3>
|
||||
<ul>
|
||||
<li><strong>Quality:</strong> We work only with stores who meet our quality standards</li>
|
||||
<li><strong>Transparency:</strong> Clear pricing, policies, and communication</li>
|
||||
<li><strong>Customer First:</strong> Your satisfaction is our priority</li>
|
||||
<li><strong>Innovation:</strong> Continuously improving our platform and services</li>
|
||||
</ul>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Learn about our mission to connect quality stores with customers worldwide",
|
||||
"meta_keywords": "about us, mission, values, platform",
|
||||
"show_in_footer": True,
|
||||
"show_in_header": True,
|
||||
"display_order": 1,
|
||||
},
|
||||
{
|
||||
"slug": "contact",
|
||||
"title": "Contact Us",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Get in Touch</h2>
|
||||
<p>We're here to help! Reach out to us through any of the following channels:</p>
|
||||
|
||||
<h3>Customer Support</h3>
|
||||
<p>For questions about orders, products, or general inquiries:</p>
|
||||
<ul>
|
||||
<li><strong>Email:</strong> support@example.com</li>
|
||||
<li><strong>Phone:</strong> 1-800-EXAMPLE (Mon-Fri, 9am-6pm EST)</li>
|
||||
<li><strong>Live Chat:</strong> Available on our website during business hours</li>
|
||||
</ul>
|
||||
|
||||
<h3>Business Inquiries</h3>
|
||||
<p>Interested in becoming a store or partnering with us?</p>
|
||||
<ul>
|
||||
<li><strong>Email:</strong> stores@example.com</li>
|
||||
</ul>
|
||||
|
||||
<h3>Office Address</h3>
|
||||
<p>
|
||||
123 Commerce Street<br>
|
||||
Suite 456<br>
|
||||
City, State 12345<br>
|
||||
United States
|
||||
</p>
|
||||
|
||||
<h3>Response Time</h3>
|
||||
<p>We typically respond to all inquiries within 24 hours during business days.</p>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Contact our customer support team for assistance with orders, products, or inquiries",
|
||||
"meta_keywords": "contact, support, customer service, help",
|
||||
"show_in_footer": True,
|
||||
"show_in_header": True,
|
||||
"display_order": 2,
|
||||
},
|
||||
{
|
||||
"slug": "faq",
|
||||
"title": "Frequently Asked Questions",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
|
||||
<h3>Orders & Shipping</h3>
|
||||
|
||||
<h4>How do I track my order?</h4>
|
||||
<p>Once your order ships, you'll receive a tracking number via email. You can also view your order status in your account dashboard.</p>
|
||||
|
||||
<h4>How long does shipping take?</h4>
|
||||
<p>Shipping times vary by store and destination. Most orders arrive within 3-7 business days. See our <a href="/shipping">Shipping Policy</a> for details.</p>
|
||||
|
||||
<h4>Do you ship internationally?</h4>
|
||||
<p>Yes! We ship to most countries worldwide. International shipping times and costs vary by destination.</p>
|
||||
|
||||
<h3>Returns & Refunds</h3>
|
||||
|
||||
<h4>What is your return policy?</h4>
|
||||
<p>Most items can be returned within 30 days of delivery. See our <a href="/returns">Return Policy</a> for complete details.</p>
|
||||
|
||||
<h4>How do I request a refund?</h4>
|
||||
<p>Contact the store directly through your order page or reach out to our support team for assistance.</p>
|
||||
|
||||
<h3>Payments</h3>
|
||||
|
||||
<h4>What payment methods do you accept?</h4>
|
||||
<p>We accept all major credit cards, PayPal, and other secure payment methods.</p>
|
||||
|
||||
<h4>Is my payment information secure?</h4>
|
||||
<p>Yes! We use industry-standard encryption and never store your full payment details.</p>
|
||||
|
||||
<h3>Account</h3>
|
||||
|
||||
<h4>Do I need an account to make a purchase?</h4>
|
||||
<p>No, you can checkout as a guest. However, creating an account lets you track orders and save your preferences.</p>
|
||||
|
||||
<h4>How do I reset my password?</h4>
|
||||
<p>Click "Forgot Password" on the login page and follow the instructions sent to your email.</p>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Find answers to common questions about orders, shipping, returns, and more",
|
||||
"meta_keywords": "faq, questions, help, support",
|
||||
"show_in_footer": True,
|
||||
"show_in_header": False,
|
||||
"display_order": 3,
|
||||
},
|
||||
{
|
||||
"slug": "shipping",
|
||||
"title": "Shipping Policy",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Shipping Policy</h2>
|
||||
|
||||
<h3>Shipping Methods</h3>
|
||||
<p>We offer multiple shipping options to meet your needs:</p>
|
||||
<ul>
|
||||
<li><strong>Standard Shipping:</strong> 5-7 business days</li>
|
||||
<li><strong>Express Shipping:</strong> 2-3 business days</li>
|
||||
<li><strong>Overnight Shipping:</strong> Next business day</li>
|
||||
</ul>
|
||||
|
||||
<h3>Shipping Costs</h3>
|
||||
<p>Shipping costs are calculated based on:</p>
|
||||
<ul>
|
||||
<li>Package weight and dimensions</li>
|
||||
<li>Destination address</li>
|
||||
<li>Selected shipping method</li>
|
||||
</ul>
|
||||
<p>Free standard shipping on orders over $50!</p>
|
||||
|
||||
<h3>Processing Time</h3>
|
||||
<p>Orders are typically processed within 1-2 business days. You'll receive a confirmation email when your order ships with tracking information.</p>
|
||||
|
||||
<h3>International Shipping</h3>
|
||||
<p>We ship to most countries worldwide. International shipping times vary by destination (typically 7-21 business days).</p>
|
||||
<ul>
|
||||
<li>Customs fees and import duties are the responsibility of the recipient</li>
|
||||
<li>International orders may be subject to customs inspection</li>
|
||||
</ul>
|
||||
|
||||
<h3>Tracking</h3>
|
||||
<p>All orders include tracking. You can monitor your package status through:</p>
|
||||
<ul>
|
||||
<li>Your account dashboard</li>
|
||||
<li>Email notifications</li>
|
||||
<li>Carrier tracking website</li>
|
||||
</ul>
|
||||
|
||||
<h3>Delivery Issues</h3>
|
||||
<p>If you experience any delivery issues, please contact us within 7 days of the expected delivery date.</p>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Learn about our shipping methods, costs, and delivery times",
|
||||
"meta_keywords": "shipping, delivery, tracking, international",
|
||||
"show_in_footer": True,
|
||||
"show_in_header": False,
|
||||
"display_order": 4,
|
||||
},
|
||||
{
|
||||
"slug": "returns",
|
||||
"title": "Return & Refund Policy",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Return & Refund Policy</h2>
|
||||
|
||||
<h3>30-Day Return Window</h3>
|
||||
<p>Most items can be returned within 30 days of delivery for a full refund or exchange.</p>
|
||||
|
||||
<h3>Return Requirements</h3>
|
||||
<p>To be eligible for a return, items must:</p>
|
||||
<ul>
|
||||
<li>Be in original condition with tags attached</li>
|
||||
<li>Include original packaging and accessories</li>
|
||||
<li>Not be used or damaged</li>
|
||||
<li>Include proof of purchase</li>
|
||||
</ul>
|
||||
|
||||
<h3>Non-Returnable Items</h3>
|
||||
<p>The following items cannot be returned:</p>
|
||||
<ul>
|
||||
<li>Personalized or custom-made items</li>
|
||||
<li>Perishable goods</li>
|
||||
<li>Health and personal care items</li>
|
||||
<li>Digital downloads</li>
|
||||
<li>Sale or clearance items (marked as final sale)</li>
|
||||
</ul>
|
||||
|
||||
<h3>How to Return</h3>
|
||||
<ol>
|
||||
<li>Log into your account and go to "Order History"</li>
|
||||
<li>Select the order and click "Request Return"</li>
|
||||
<li>Choose items and provide reason for return</li>
|
||||
<li>Print the prepaid return label</li>
|
||||
<li>Pack items securely and attach label</li>
|
||||
<li>Drop off at any authorized carrier location</li>
|
||||
</ol>
|
||||
|
||||
<h3>Refund Process</h3>
|
||||
<p>Once we receive your return:</p>
|
||||
<ul>
|
||||
<li>We'll inspect the items (2-3 business days)</li>
|
||||
<li>Approved refunds are processed within 5-7 business days</li>
|
||||
<li>Refunds go to original payment method</li>
|
||||
<li>You'll receive email confirmation</li>
|
||||
</ul>
|
||||
|
||||
<h3>Return Shipping</h3>
|
||||
<p>Return shipping is free for defective or incorrect items. For other returns, a $5.99 return shipping fee will be deducted from your refund.</p>
|
||||
|
||||
<h3>Exchanges</h3>
|
||||
<p>We offer free exchanges for different sizes or colors of the same item. Contact support to arrange an exchange.</p>
|
||||
|
||||
<h3>Damaged or Defective Items</h3>
|
||||
<p>If you receive a damaged or defective item, please contact us within 7 days of delivery with photos. We'll send a replacement or full refund immediately.</p>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Our 30-day return policy ensures your satisfaction with every purchase",
|
||||
"meta_keywords": "returns, refunds, exchange, policy",
|
||||
"show_in_footer": True,
|
||||
"show_in_header": False,
|
||||
"display_order": 5,
|
||||
},
|
||||
{
|
||||
"slug": "privacy",
|
||||
"title": "Privacy Policy",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Privacy Policy</h2>
|
||||
<p><em>Last Updated: [Date]</em></p>
|
||||
|
||||
<h3>Information We Collect</h3>
|
||||
<p>We collect information you provide directly:</p>
|
||||
<ul>
|
||||
<li>Name, email address, and contact information</li>
|
||||
<li>Shipping and billing addresses</li>
|
||||
<li>Payment information (processed securely by our payment processor)</li>
|
||||
<li>Order history and preferences</li>
|
||||
</ul>
|
||||
|
||||
<p>We automatically collect:</p>
|
||||
<ul>
|
||||
<li>Browser type and device information</li>
|
||||
<li>IP address and location data</li>
|
||||
<li>Cookies and similar tracking technologies</li>
|
||||
<li>Usage data and analytics</li>
|
||||
</ul>
|
||||
|
||||
<h3>How We Use Your Information</h3>
|
||||
<p>We use your information to:</p>
|
||||
<ul>
|
||||
<li>Process and fulfill your orders</li>
|
||||
<li>Communicate about orders and account</li>
|
||||
<li>Provide customer support</li>
|
||||
<li>Send marketing communications (with your consent)</li>
|
||||
<li>Improve our platform and services</li>
|
||||
<li>Prevent fraud and enhance security</li>
|
||||
</ul>
|
||||
|
||||
<h3>Information Sharing</h3>
|
||||
<p>We share your information only when necessary:</p>
|
||||
<ul>
|
||||
<li><strong>Stores:</strong> To fulfill your orders</li>
|
||||
<li><strong>Service Providers:</strong> Payment processors, shipping carriers, analytics</li>
|
||||
<li><strong>Legal Requirements:</strong> When required by law</li>
|
||||
</ul>
|
||||
<p>We never sell your personal information to third parties.</p>
|
||||
|
||||
<h3>Your Rights</h3>
|
||||
<p>You have the right to:</p>
|
||||
<ul>
|
||||
<li>Access your personal data</li>
|
||||
<li>Correct inaccurate information</li>
|
||||
<li>Request deletion of your data</li>
|
||||
<li>Opt-out of marketing communications</li>
|
||||
<li>Export your data</li>
|
||||
</ul>
|
||||
|
||||
<h3>Data Security</h3>
|
||||
<p>We implement industry-standard security measures including:</p>
|
||||
<ul>
|
||||
<li>SSL/TLS encryption for data transmission</li>
|
||||
<li>Secure password hashing</li>
|
||||
<li>Regular security audits</li>
|
||||
<li>Limited employee access to personal data</li>
|
||||
</ul>
|
||||
|
||||
<h3>Cookies</h3>
|
||||
<p>We use cookies to enhance your experience. You can control cookies through your browser settings.</p>
|
||||
|
||||
<h3>Children's Privacy</h3>
|
||||
<p>Our platform is not intended for children under 13. We do not knowingly collect information from children.</p>
|
||||
|
||||
<h3>Changes to This Policy</h3>
|
||||
<p>We may update this policy periodically. Significant changes will be communicated via email.</p>
|
||||
|
||||
<h3>Contact Us</h3>
|
||||
<p>For privacy-related questions, contact us at privacy@example.com</p>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Learn how we collect, use, and protect your personal information",
|
||||
"meta_keywords": "privacy, data protection, security, policy",
|
||||
"show_in_footer": False,
|
||||
"show_in_header": False,
|
||||
"show_in_legal": True,
|
||||
"display_order": 6,
|
||||
},
|
||||
{
|
||||
"slug": "terms",
|
||||
"title": "Terms of Service",
|
||||
"content": """
|
||||
<div class="prose-content">
|
||||
<h2>Terms of Service</h2>
|
||||
<p><em>Last Updated: [Date]</em></p>
|
||||
|
||||
<h3>1. Acceptance of Terms</h3>
|
||||
<p>By accessing and using this platform, you accept and agree to be bound by these Terms of Service.</p>
|
||||
|
||||
<h3>2. Account Registration</h3>
|
||||
<p>You must:</p>
|
||||
<ul>
|
||||
<li>Be at least 18 years old</li>
|
||||
<li>Provide accurate and complete information</li>
|
||||
<li>Maintain the security of your account</li>
|
||||
<li>Notify us of unauthorized access</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. User Conduct</h3>
|
||||
<p>You agree not to:</p>
|
||||
<ul>
|
||||
<li>Violate any laws or regulations</li>
|
||||
<li>Infringe on intellectual property rights</li>
|
||||
<li>Transmit harmful code or viruses</li>
|
||||
<li>Harass or harm other users</li>
|
||||
<li>Engage in fraudulent activities</li>
|
||||
<li>Scrape or data mine our platform</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Product Listings</h3>
|
||||
<p>Product information is provided by stores. While we strive for accuracy:</p>
|
||||
<ul>
|
||||
<li>Descriptions may contain errors</li>
|
||||
<li>Prices are subject to change</li>
|
||||
<li>Availability is not guaranteed</li>
|
||||
<li>Images are representative</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Orders and Payment</h3>
|
||||
<ul>
|
||||
<li>All orders are subject to acceptance</li>
|
||||
<li>We reserve the right to refuse or cancel orders</li>
|
||||
<li>Prices include applicable taxes unless stated otherwise</li>
|
||||
<li>Payment must be received before order processing</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Intellectual Property</h3>
|
||||
<p>All content on this platform is protected by copyright, trademark, and other laws. You may not:</p>
|
||||
<ul>
|
||||
<li>Reproduce or distribute our content</li>
|
||||
<li>Create derivative works</li>
|
||||
<li>Use our trademarks without permission</li>
|
||||
</ul>
|
||||
|
||||
<h3>7. Disclaimer of Warranties</h3>
|
||||
<p>This platform is provided "as is" without warranties of any kind, express or implied.</p>
|
||||
|
||||
<h3>8. Limitation of Liability</h3>
|
||||
<p>We are not liable for indirect, incidental, or consequential damages arising from your use of the platform.</p>
|
||||
|
||||
<h3>9. Indemnification</h3>
|
||||
<p>You agree to indemnify and hold us harmless from claims arising from your use of the platform or violation of these terms.</p>
|
||||
|
||||
<h3>10. Dispute Resolution</h3>
|
||||
<p>Disputes will be resolved through binding arbitration in accordance with the rules of the American Arbitration Association.</p>
|
||||
|
||||
<h3>11. Governing Law</h3>
|
||||
<p>These terms are governed by the laws of [State/Country], without regard to conflict of law principles.</p>
|
||||
|
||||
<h3>12. Changes to Terms</h3>
|
||||
<p>We may modify these terms at any time. Continued use constitutes acceptance of modified terms.</p>
|
||||
|
||||
<h3>13. Contact</h3>
|
||||
<p>For questions about these terms, contact legal@example.com</p>
|
||||
</div>
|
||||
""",
|
||||
"meta_description": "Read our terms of service governing the use of our platform",
|
||||
"meta_keywords": "terms, conditions, legal, agreement",
|
||||
"show_in_footer": False,
|
||||
"show_in_header": False,
|
||||
"show_in_legal": True,
|
||||
"display_order": 7,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SCRIPT FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def create_default_pages(db: Session) -> None:
|
||||
"""
|
||||
Create default platform content pages.
|
||||
|
||||
This function is idempotent - it will skip pages that already exist.
|
||||
"""
|
||||
print("\n" + "=" * 70)
|
||||
print("Creating Default Platform Content Pages (CMS)")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
# Resolve OMS platform for platform_id
|
||||
oms_platform = db.execute(
|
||||
select(Platform).where(Platform.code == "oms")
|
||||
).scalar_one_or_none()
|
||||
if not oms_platform:
|
||||
print(" ⚠️ OMS platform not found. Run init_production.py first.")
|
||||
return
|
||||
platform_id = oms_platform.id
|
||||
|
||||
created_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for page_data in DEFAULT_PAGES:
|
||||
# Check if page already exists (platform default with this slug)
|
||||
existing = db.execute(
|
||||
select(ContentPage).where(
|
||||
ContentPage.store_id == None, ContentPage.slug == page_data["slug"]
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
print(
|
||||
f" ⏭️ Skipped: {page_data['title']} (/{page_data['slug']}) - already exists"
|
||||
)
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Create new platform default page
|
||||
page = ContentPage(
|
||||
platform_id=platform_id,
|
||||
store_id=None, # Platform default
|
||||
slug=page_data["slug"],
|
||||
title=page_data["title"],
|
||||
content=page_data["content"],
|
||||
content_format="html",
|
||||
meta_description=page_data["meta_description"],
|
||||
meta_keywords=page_data["meta_keywords"],
|
||||
is_published=True,
|
||||
published_at=datetime.now(UTC),
|
||||
show_in_footer=page_data.get("show_in_footer", True),
|
||||
show_in_header=page_data.get("show_in_header", False),
|
||||
show_in_legal=page_data.get("show_in_legal", False),
|
||||
display_order=page_data["display_order"],
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
db.add(page)
|
||||
print(f" ✅ Created: {page_data['title']} (/{page_data['slug']})")
|
||||
created_count += 1
|
||||
|
||||
db.commit()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("Summary:")
|
||||
print(f" Created: {created_count} pages")
|
||||
print(f" Skipped: {skipped_count} pages (already exist)")
|
||||
print(f" Total: {created_count + skipped_count} pages")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
if created_count > 0:
|
||||
print("✅ Default platform content pages created successfully!\n")
|
||||
print("Next steps:")
|
||||
print(
|
||||
" 1. View pages at: /about, /contact, /faq, /shipping, /returns, /privacy, /terms"
|
||||
)
|
||||
print(" 2. Stores can override these pages through the store dashboard")
|
||||
print(" 3. Edit platform defaults through the admin panel\n")
|
||||
else:
|
||||
print("ℹ️ All default pages already exist. No changes made.\n")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MAIN EXECUTION
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def main():
|
||||
"""Main execution function."""
|
||||
print("\n🚀 Starting Default Content Pages Creation Script...\n")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
create_default_pages(db)
|
||||
print("✅ Script completed successfully!\n")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}\n")
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
541
scripts/seed/create_platform_pages.py
Executable file
541
scripts/seed/create_platform_pages.py
Executable file
@@ -0,0 +1,541 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create Platform Content Pages
|
||||
|
||||
This script creates default platform-level content pages:
|
||||
- Platform Homepage (slug='platform_homepage')
|
||||
- About Us (slug='about')
|
||||
- FAQ (slug='faq')
|
||||
- Terms of Service (slug='terms')
|
||||
- Privacy Policy (slug='privacy')
|
||||
- Contact Us (slug='contact')
|
||||
|
||||
All pages are created with store_id=NULL (platform-level defaults).
|
||||
|
||||
Usage:
|
||||
python scripts/create_platform_pages.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# Register all models with SQLAlchemy so string-based relationships resolve
|
||||
for _mod in [
|
||||
"app.modules.billing.models",
|
||||
"app.modules.inventory.models",
|
||||
"app.modules.cart.models",
|
||||
"app.modules.messaging.models",
|
||||
"app.modules.loyalty.models",
|
||||
"app.modules.catalog.models",
|
||||
"app.modules.customers.models",
|
||||
"app.modules.orders.models",
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.cms.services import content_page_service
|
||||
from app.modules.tenancy.models import Platform
|
||||
|
||||
|
||||
def create_platform_pages():
|
||||
"""Create default platform content pages."""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
print("=" * 80)
|
||||
print("CREATING PLATFORM CONTENT PAGES")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
# Import ContentPage for checking existing pages
|
||||
from app.modules.cms.models import ContentPage
|
||||
|
||||
# Resolve OMS platform
|
||||
oms_platform = db.execute(
|
||||
select(Platform).where(Platform.code == "oms")
|
||||
).scalar_one_or_none()
|
||||
if not oms_platform:
|
||||
print(" ⚠️ OMS platform not found. Run init_production.py first.")
|
||||
return
|
||||
platform_id = oms_platform.id
|
||||
|
||||
# ========================================================================
|
||||
# 1. PLATFORM HOMEPAGE
|
||||
# ========================================================================
|
||||
print("1. Creating Platform Homepage...")
|
||||
|
||||
# Check if already exists
|
||||
existing = (
|
||||
db.query(ContentPage)
|
||||
.filter_by(store_id=None, slug="platform_homepage")
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
print(
|
||||
f" ⚠️ Skipped: Platform Homepage - already exists (ID: {existing.id})"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
homepage = content_page_service.create_page(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
slug="platform_homepage",
|
||||
title="Welcome to Our Multi-Store Marketplace",
|
||||
content="""
|
||||
<p class="lead">
|
||||
Connect stores with customers worldwide. Build your online store and reach millions of shoppers.
|
||||
</p>
|
||||
<p>
|
||||
Our platform empowers entrepreneurs to launch their own branded e-commerce stores
|
||||
with minimal effort and maximum impact.
|
||||
</p>
|
||||
""",
|
||||
template="modern", # Uses platform/homepage-modern.html
|
||||
store_id=None, # Platform-level page
|
||||
is_published=True,
|
||||
show_in_header=False, # Homepage is not in menu (it's the root)
|
||||
show_in_footer=False,
|
||||
display_order=0,
|
||||
meta_description="Leading multi-store marketplace platform. Connect with thousands of stores and discover millions of products.",
|
||||
meta_keywords="marketplace, multi-store, e-commerce, online shopping, platform",
|
||||
)
|
||||
print(f" ✅ Created: {homepage.title} (/{homepage.slug})")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error: Platform Homepage - {str(e)}")
|
||||
|
||||
# ========================================================================
|
||||
# 2. ABOUT US
|
||||
# ========================================================================
|
||||
print("2. Creating About Us page...")
|
||||
|
||||
existing = db.query(ContentPage).filter_by(store_id=None, slug="about").first()
|
||||
if existing:
|
||||
print(f" ⚠️ Skipped: About Us - already exists (ID: {existing.id})")
|
||||
else:
|
||||
try:
|
||||
about = content_page_service.create_page(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
slug="about",
|
||||
title="About Us",
|
||||
content="""
|
||||
<h2>Our Mission</h2>
|
||||
<p>
|
||||
We're on a mission to democratize e-commerce by providing powerful,
|
||||
easy-to-use tools for entrepreneurs worldwide.
|
||||
</p>
|
||||
|
||||
<h2>Our Story</h2>
|
||||
<p>
|
||||
Founded in 2024, our platform has grown to serve over 10,000 active stores
|
||||
and millions of customers around the globe. We believe that everyone should
|
||||
have the opportunity to build and grow their own online business.
|
||||
</p>
|
||||
|
||||
<h2>Why Choose Us?</h2>
|
||||
<ul>
|
||||
<li><strong>Easy to Start:</strong> Launch your store in minutes, not months</li>
|
||||
<li><strong>Powerful Tools:</strong> Everything you need to succeed in one platform</li>
|
||||
<li><strong>Scalable:</strong> Grow from startup to enterprise seamlessly</li>
|
||||
<li><strong>Reliable:</strong> 99.9% uptime guarantee with 24/7 support</li>
|
||||
</ul>
|
||||
|
||||
<h2>Our Values</h2>
|
||||
<ul>
|
||||
<li><strong>Innovation:</strong> We constantly improve and evolve our platform</li>
|
||||
<li><strong>Transparency:</strong> No hidden fees, no surprises</li>
|
||||
<li><strong>Community:</strong> We succeed when our stores succeed</li>
|
||||
<li><strong>Excellence:</strong> We strive for the highest quality in everything we do</li>
|
||||
</ul>
|
||||
""",
|
||||
store_id=None,
|
||||
is_published=True,
|
||||
show_in_header=True, # Show in header navigation
|
||||
show_in_footer=True, # Show in footer
|
||||
display_order=1,
|
||||
meta_description="Learn about our mission to democratize e-commerce and empower entrepreneurs worldwide.",
|
||||
meta_keywords="about us, mission, vision, values, merchant",
|
||||
)
|
||||
print(f" ✅ Created: {about.title} (/{about.slug})")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error: About Us - {str(e)}")
|
||||
|
||||
# ========================================================================
|
||||
# 3. FAQ
|
||||
# ========================================================================
|
||||
print("3. Creating FAQ page...")
|
||||
|
||||
existing = db.query(ContentPage).filter_by(store_id=None, slug="faq").first()
|
||||
if existing:
|
||||
print(f" ⚠️ Skipped: FAQ - already exists (ID: {existing.id})")
|
||||
else:
|
||||
try:
|
||||
faq = content_page_service.create_page(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
slug="faq",
|
||||
title="Frequently Asked Questions",
|
||||
content="""
|
||||
<h2>Getting Started</h2>
|
||||
|
||||
<h3>How do I create a store account?</h3>
|
||||
<p>
|
||||
Contact our sales team to get started. We'll set up your account and provide
|
||||
you with everything you need to launch your store.
|
||||
</p>
|
||||
|
||||
<h3>How long does it take to set up my store?</h3>
|
||||
<p>
|
||||
Most stores can launch their store in less than 24 hours. Our team will guide
|
||||
you through the setup process step by step.
|
||||
</p>
|
||||
|
||||
<h2>Pricing & Payment</h2>
|
||||
|
||||
<h3>What are your pricing plans?</h3>
|
||||
<p>
|
||||
We offer flexible pricing plans based on your business needs. Contact us for
|
||||
detailed pricing information and to find the plan that's right for you.
|
||||
</p>
|
||||
|
||||
<h3>When do I get paid?</h3>
|
||||
<p>
|
||||
Payments are processed weekly, with funds typically reaching your account
|
||||
within 2-3 business days.
|
||||
</p>
|
||||
|
||||
<h2>Features & Support</h2>
|
||||
|
||||
<h3>Can I customize my store's appearance?</h3>
|
||||
<p>
|
||||
Yes! Our platform supports full theme customization including colors, fonts,
|
||||
logos, and layouts. Make your store truly yours.
|
||||
</p>
|
||||
|
||||
<h3>What kind of support do you provide?</h3>
|
||||
<p>
|
||||
We offer 24/7 email support for all stores, with priority phone support
|
||||
available for enterprise plans.
|
||||
</p>
|
||||
|
||||
<h2>Technical Questions</h2>
|
||||
|
||||
<h3>Do I need technical knowledge to use the platform?</h3>
|
||||
<p>
|
||||
No! Our platform is designed to be user-friendly for everyone. However, if you
|
||||
want to customize advanced features, our documentation and support team are here to help.
|
||||
</p>
|
||||
|
||||
<h3>Can I integrate with other tools?</h3>
|
||||
<p>
|
||||
Yes, we support integrations with popular payment gateways, shipping providers,
|
||||
and marketing tools.
|
||||
</p>
|
||||
""",
|
||||
store_id=None,
|
||||
is_published=True,
|
||||
show_in_header=True, # Show in header navigation
|
||||
show_in_footer=True,
|
||||
display_order=2,
|
||||
meta_description="Find answers to common questions about our marketplace platform.",
|
||||
meta_keywords="faq, frequently asked questions, help, support",
|
||||
)
|
||||
print(f" ✅ Created: {faq.title} (/{faq.slug})")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error: FAQ - {str(e)}")
|
||||
|
||||
# ========================================================================
|
||||
# 4. CONTACT US
|
||||
# ========================================================================
|
||||
print("4. Creating Contact Us page...")
|
||||
|
||||
existing = (
|
||||
db.query(ContentPage).filter_by(store_id=None, slug="contact").first()
|
||||
)
|
||||
if existing:
|
||||
print(f" ⚠️ Skipped: Contact Us - already exists (ID: {existing.id})")
|
||||
else:
|
||||
try:
|
||||
contact = content_page_service.create_page(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
slug="contact",
|
||||
title="Contact Us",
|
||||
content="""
|
||||
<h2>Get in Touch</h2>
|
||||
<p>
|
||||
We'd love to hear from you! Whether you have questions about our platform,
|
||||
need technical support, or want to discuss partnership opportunities, our
|
||||
team is here to help.
|
||||
</p>
|
||||
|
||||
<h2>Contact Information</h2>
|
||||
<ul>
|
||||
<li><strong>Email:</strong> support@marketplace.com</li>
|
||||
<li><strong>Phone:</strong> +1 (555) 123-4567</li>
|
||||
<li><strong>Hours:</strong> Monday - Friday, 9 AM - 6 PM PST</li>
|
||||
</ul>
|
||||
|
||||
<h2>Office Address</h2>
|
||||
<p>
|
||||
123 Business Street, Suite 100<br>
|
||||
San Francisco, CA 94102<br>
|
||||
United States
|
||||
</p>
|
||||
|
||||
<h2>Sales Inquiries</h2>
|
||||
<p>
|
||||
Interested in launching your store on our platform?<br>
|
||||
Email: <a href="mailto:sales@marketplace.com">sales@marketplace.com</a>
|
||||
</p>
|
||||
|
||||
<h2>Technical Support</h2>
|
||||
<p>
|
||||
Need help with your store?<br>
|
||||
Email: <a href="mailto:support@marketplace.com">support@marketplace.com</a><br>
|
||||
24/7 email support for all stores
|
||||
</p>
|
||||
|
||||
<h2>Media & Press</h2>
|
||||
<p>
|
||||
For media inquiries and press releases:<br>
|
||||
Email: <a href="mailto:press@marketplace.com">press@marketplace.com</a>
|
||||
</p>
|
||||
""",
|
||||
store_id=None,
|
||||
is_published=True,
|
||||
show_in_header=True, # Show in header navigation
|
||||
show_in_footer=True,
|
||||
display_order=3,
|
||||
meta_description="Get in touch with our team. We're here to help you succeed.",
|
||||
meta_keywords="contact, support, email, phone, address",
|
||||
)
|
||||
print(f" ✅ Created: {contact.title} (/{contact.slug})")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error: Contact Us - {str(e)}")
|
||||
|
||||
# ========================================================================
|
||||
# 5. TERMS OF SERVICE
|
||||
# ========================================================================
|
||||
print("5. Creating Terms of Service page...")
|
||||
|
||||
existing = db.query(ContentPage).filter_by(store_id=None, slug="terms").first()
|
||||
if existing:
|
||||
print(
|
||||
f" ⚠️ Skipped: Terms of Service - already exists (ID: {existing.id})"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
terms = content_page_service.create_page(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
slug="terms",
|
||||
title="Terms of Service",
|
||||
content="""
|
||||
<p><em>Last updated: January 1, 2024</em></p>
|
||||
|
||||
<h2>1. Acceptance of Terms</h2>
|
||||
<p>
|
||||
By accessing and using this marketplace platform, you accept and agree to be
|
||||
bound by the terms and provisions of this agreement.
|
||||
</p>
|
||||
|
||||
<h2>2. Use License</h2>
|
||||
<p>
|
||||
Permission is granted to temporarily access the materials on our platform for
|
||||
personal, non-commercial transitory viewing only.
|
||||
</p>
|
||||
|
||||
<h2>3. Account Terms</h2>
|
||||
<ul>
|
||||
<li>You must be at least 18 years old to use our platform</li>
|
||||
<li>You must provide accurate and complete registration information</li>
|
||||
<li>You are responsible for maintaining the security of your account</li>
|
||||
<li>You are responsible for all activities under your account</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Store Responsibilities</h2>
|
||||
<ul>
|
||||
<li>Provide accurate product information and pricing</li>
|
||||
<li>Honor all orders and commitments made through the platform</li>
|
||||
<li>Comply with all applicable laws and regulations</li>
|
||||
<li>Maintain appropriate customer service standards</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Prohibited Activities</h2>
|
||||
<p>You may not use our platform to:</p>
|
||||
<ul>
|
||||
<li>Engage in any fraudulent or illegal activities</li>
|
||||
<li>Violate any intellectual property rights</li>
|
||||
<li>Transmit harmful code or malware</li>
|
||||
<li>Interfere with platform operations</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Termination</h2>
|
||||
<p>
|
||||
We reserve the right to terminate or suspend your account at any time for
|
||||
violation of these terms.
|
||||
</p>
|
||||
|
||||
<h2>7. Limitation of Liability</h2>
|
||||
<p>
|
||||
In no event shall our merchant be liable for any damages arising out of the
|
||||
use or inability to use our platform.
|
||||
</p>
|
||||
|
||||
<h2>8. Changes to Terms</h2>
|
||||
<p>
|
||||
We reserve the right to modify these terms at any time. We will notify users
|
||||
of any changes via email.
|
||||
</p>
|
||||
|
||||
<h2>9. Contact</h2>
|
||||
<p>
|
||||
If you have any questions about these Terms, please contact us at
|
||||
<a href="mailto:legal@marketplace.com">legal@marketplace.com</a>.
|
||||
</p>
|
||||
""",
|
||||
store_id=None,
|
||||
is_published=True,
|
||||
show_in_header=False, # Too legal for header
|
||||
show_in_footer=True, # Show in footer
|
||||
display_order=10,
|
||||
meta_description="Read our terms of service and platform usage policies.",
|
||||
meta_keywords="terms of service, terms, legal, policy, agreement",
|
||||
)
|
||||
print(f" ✅ Created: {terms.title} (/{terms.slug})")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error: Terms of Service - {str(e)}")
|
||||
|
||||
# ========================================================================
|
||||
# 6. PRIVACY POLICY
|
||||
# ========================================================================
|
||||
print("6. Creating Privacy Policy page...")
|
||||
|
||||
existing = (
|
||||
db.query(ContentPage).filter_by(store_id=None, slug="privacy").first()
|
||||
)
|
||||
if existing:
|
||||
print(f" ⚠️ Skipped: Privacy Policy - already exists (ID: {existing.id})")
|
||||
else:
|
||||
try:
|
||||
privacy = content_page_service.create_page(
|
||||
db,
|
||||
platform_id=platform_id,
|
||||
slug="privacy",
|
||||
title="Privacy Policy",
|
||||
content="""
|
||||
<p><em>Last updated: January 1, 2024</em></p>
|
||||
|
||||
<h2>1. Information We Collect</h2>
|
||||
<p>We collect information you provide directly to us, including:</p>
|
||||
<ul>
|
||||
<li>Name, email address, and contact information</li>
|
||||
<li>Payment and billing information</li>
|
||||
<li>Store and product information</li>
|
||||
<li>Communications with us</li>
|
||||
</ul>
|
||||
|
||||
<h2>2. How We Use Your Information</h2>
|
||||
<p>We use the information we collect to:</p>
|
||||
<ul>
|
||||
<li>Provide, maintain, and improve our services</li>
|
||||
<li>Process transactions and send related information</li>
|
||||
<li>Send technical notices and support messages</li>
|
||||
<li>Respond to your comments and questions</li>
|
||||
<li>Monitor and analyze trends and usage</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Information Sharing</h2>
|
||||
<p>
|
||||
We do not sell your personal information. We may share information with:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Service providers who help us operate our platform</li>
|
||||
<li>Law enforcement when required by law</li>
|
||||
<li>Other parties with your consent</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Data Security</h2>
|
||||
<p>
|
||||
We implement appropriate security measures to protect your personal information.
|
||||
However, no method of transmission over the internet is 100% secure.
|
||||
</p>
|
||||
|
||||
<h2>5. Your Rights</h2>
|
||||
<p>You have the right to:</p>
|
||||
<ul>
|
||||
<li>Access your personal information</li>
|
||||
<li>Correct inaccurate information</li>
|
||||
<li>Request deletion of your information</li>
|
||||
<li>Opt-out of marketing communications</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Cookies</h2>
|
||||
<p>
|
||||
We use cookies and similar tracking technologies to track activity on our
|
||||
platform and hold certain information. You can instruct your browser to
|
||||
refuse cookies.
|
||||
</p>
|
||||
|
||||
<h2>7. Changes to This Policy</h2>
|
||||
<p>
|
||||
We may update this privacy policy from time to time. We will notify you of
|
||||
any changes by posting the new policy on this page.
|
||||
</p>
|
||||
|
||||
<h2>8. Contact Us</h2>
|
||||
<p>
|
||||
If you have questions about this Privacy Policy, please contact us at
|
||||
<a href="mailto:privacy@marketplace.com">privacy@marketplace.com</a>.
|
||||
</p>
|
||||
""",
|
||||
store_id=None,
|
||||
is_published=True,
|
||||
show_in_header=False, # Too legal for header
|
||||
show_in_footer=True, # Show in footer
|
||||
display_order=11,
|
||||
meta_description="Learn how we collect, use, and protect your personal information.",
|
||||
meta_keywords="privacy policy, privacy, data protection, gdpr, personal information",
|
||||
)
|
||||
print(f" ✅ Created: {privacy.title} (/{privacy.slug})")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Error: Privacy Policy - {str(e)}")
|
||||
|
||||
db.commit()
|
||||
|
||||
print()
|
||||
print("=" * 80)
|
||||
print("✅ Platform pages creation completed successfully!")
|
||||
print("=" * 80)
|
||||
print()
|
||||
print("Created pages:")
|
||||
print(" - Platform Homepage: http://localhost:8000/")
|
||||
print(" - About Us: http://localhost:8000/about")
|
||||
print(" - FAQ: http://localhost:8000/faq")
|
||||
print(" - Contact Us: http://localhost:8000/contact")
|
||||
print(" - Terms of Service: http://localhost:8000/terms")
|
||||
print(" - Privacy Policy: http://localhost:8000/privacy")
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_platform_pages()
|
||||
0
scripts/seed/init_db.py
Normal file
0
scripts/seed/init_db.py
Normal file
137
scripts/seed/init_log_settings.py
Normal file
137
scripts/seed/init_log_settings.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Initialize default log settings in database.
|
||||
|
||||
Run this script to create default logging configuration settings.
|
||||
"""
|
||||
|
||||
# Register all models with SQLAlchemy so string-based relationships resolve
|
||||
for _mod in [
|
||||
"app.modules.billing.models",
|
||||
"app.modules.inventory.models",
|
||||
"app.modules.cart.models",
|
||||
"app.modules.messaging.models",
|
||||
"app.modules.loyalty.models",
|
||||
"app.modules.catalog.models",
|
||||
"app.modules.customers.models",
|
||||
"app.modules.orders.models",
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.tenancy.models import AdminSetting
|
||||
|
||||
|
||||
def init_log_settings():
|
||||
"""Create default log settings if they don't exist."""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
settings_to_create = [
|
||||
{
|
||||
"key": "log_level",
|
||||
"value": "INFO",
|
||||
"value_type": "string",
|
||||
"category": "logging",
|
||||
"description": "Application log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
||||
"is_public": False,
|
||||
"is_encrypted": False,
|
||||
},
|
||||
{
|
||||
"key": "log_file_max_size_mb",
|
||||
"value": "10",
|
||||
"value_type": "integer",
|
||||
"category": "logging",
|
||||
"description": "Maximum log file size in MB before rotation",
|
||||
"is_public": False,
|
||||
"is_encrypted": False,
|
||||
},
|
||||
{
|
||||
"key": "log_file_backup_count",
|
||||
"value": "5",
|
||||
"value_type": "integer",
|
||||
"category": "logging",
|
||||
"description": "Number of rotated log files to keep",
|
||||
"is_public": False,
|
||||
"is_encrypted": False,
|
||||
},
|
||||
{
|
||||
"key": "db_log_retention_days",
|
||||
"value": "30",
|
||||
"value_type": "integer",
|
||||
"category": "logging",
|
||||
"description": "Number of days to retain logs in database",
|
||||
"is_public": False,
|
||||
"is_encrypted": False,
|
||||
},
|
||||
{
|
||||
"key": "file_logging_enabled",
|
||||
"value": "true",
|
||||
"value_type": "boolean",
|
||||
"category": "logging",
|
||||
"description": "Enable file-based logging",
|
||||
"is_public": False,
|
||||
"is_encrypted": False,
|
||||
},
|
||||
{
|
||||
"key": "db_logging_enabled",
|
||||
"value": "true",
|
||||
"value_type": "boolean",
|
||||
"category": "logging",
|
||||
"description": "Enable database logging for critical events",
|
||||
"is_public": False,
|
||||
"is_encrypted": False,
|
||||
},
|
||||
]
|
||||
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
|
||||
for setting_data in settings_to_create:
|
||||
existing = (
|
||||
db.query(AdminSetting)
|
||||
.filter(AdminSetting.key == setting_data["key"])
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing:
|
||||
print(
|
||||
f"✓ Setting '{setting_data['key']}' already exists (value: {existing.value})"
|
||||
)
|
||||
updated_count += 1
|
||||
else:
|
||||
setting = AdminSetting(**setting_data)
|
||||
db.add(setting)
|
||||
created_count += 1
|
||||
print(
|
||||
f"✓ Created setting '{setting_data['key']}' = {setting_data['value']}"
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("LOG SETTINGS INITIALIZATION COMPLETE")
|
||||
print("=" * 70)
|
||||
print(f" Created: {created_count} settings")
|
||||
print(f" Existing: {updated_count} settings")
|
||||
print(f" Total: {len(settings_to_create)} settings")
|
||||
print("=" * 70)
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"Error initializing log settings: {e}")
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 70)
|
||||
print("INITIALIZING LOG SETTINGS")
|
||||
print("=" * 70)
|
||||
init_log_settings()
|
||||
649
scripts/seed/init_production.py
Normal file
649
scripts/seed/init_production.py
Normal file
@@ -0,0 +1,649 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Production Database Initialization for Wizamart Platform
|
||||
|
||||
This script initializes ESSENTIAL data required for production:
|
||||
- Platform admin user
|
||||
- Default store roles and permissions
|
||||
- Admin settings
|
||||
- Platform configuration
|
||||
|
||||
This is SAFE to run in production and should be run after migrations.
|
||||
|
||||
Usage:
|
||||
make init-prod
|
||||
|
||||
This script is idempotent - safe to run multiple times.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import (
|
||||
print_environment_info,
|
||||
settings,
|
||||
validate_production_settings,
|
||||
)
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.environment import is_production
|
||||
from app.modules.tenancy.services.permission_discovery_service import (
|
||||
permission_discovery_service,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from app.modules.tenancy.models import AdminSetting, Platform
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.billing.models.subscription import SubscriptionTier
|
||||
|
||||
# Register all models with SQLAlchemy so string-based relationships resolve
|
||||
for _mod in [
|
||||
"app.modules.billing.models",
|
||||
"app.modules.inventory.models",
|
||||
"app.modules.cart.models",
|
||||
"app.modules.messaging.models",
|
||||
"app.modules.loyalty.models",
|
||||
"app.modules.catalog.models",
|
||||
"app.modules.customers.models",
|
||||
"app.modules.orders.models",
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def print_header(text: str):
|
||||
"""Print formatted header."""
|
||||
print("\n" + "=" * 70)
|
||||
print(f" {text}")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def print_step(step: int, text: str):
|
||||
"""Print step indicator."""
|
||||
print(f"\n[{step}] {text}")
|
||||
|
||||
|
||||
def print_success(text: str):
|
||||
"""Print success message."""
|
||||
print(f" ✓ {text}")
|
||||
|
||||
|
||||
def print_warning(text: str):
|
||||
"""Print warning message."""
|
||||
print(f" ⚠ {text}")
|
||||
|
||||
|
||||
def print_error(text: str):
|
||||
"""Print error message."""
|
||||
print(f" ✗ {text}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# INITIALIZATION FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def create_admin_user(db: Session, auth_manager: AuthManager) -> User:
|
||||
"""Create or get the platform admin user."""
|
||||
|
||||
# Check if admin already exists
|
||||
admin = db.execute(
|
||||
select(User).where(User.email == settings.admin_email)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if admin:
|
||||
print_warning(f"Admin user already exists: {admin.email}")
|
||||
return admin
|
||||
|
||||
# Create new admin
|
||||
hashed_password = auth_manager.hash_password(settings.admin_password)
|
||||
|
||||
admin = User(
|
||||
username=settings.admin_username,
|
||||
email=settings.admin_email,
|
||||
hashed_password=hashed_password,
|
||||
role="admin",
|
||||
is_super_admin=True,
|
||||
first_name=settings.admin_first_name,
|
||||
last_name=settings.admin_last_name,
|
||||
is_active=True,
|
||||
is_email_verified=True,
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
db.add(admin)
|
||||
db.flush()
|
||||
|
||||
print_success(f"Created admin user: {admin.email}")
|
||||
return admin
|
||||
|
||||
|
||||
def create_default_platforms(db: Session) -> list[Platform]:
|
||||
"""Create all default platforms (OMS, Main, Loyalty+)."""
|
||||
|
||||
platform_defs = [
|
||||
{
|
||||
"code": "oms",
|
||||
"name": "Wizamart OMS",
|
||||
"description": "Order Management System for multi-store e-commerce",
|
||||
"domain": settings.platform_domain,
|
||||
"path_prefix": "oms",
|
||||
"default_language": "fr",
|
||||
"supported_languages": ["fr", "de", "en"],
|
||||
"settings": {},
|
||||
"theme_config": {},
|
||||
},
|
||||
{
|
||||
"code": "main",
|
||||
"name": "Wizamart",
|
||||
"description": "Main marketing site showcasing all Wizamart platforms",
|
||||
"domain": "wizamart.lu",
|
||||
"path_prefix": None,
|
||||
"default_language": "fr",
|
||||
"supported_languages": ["fr", "de", "en"],
|
||||
"settings": {"is_marketing_site": True},
|
||||
"theme_config": {"primary_color": "#2563EB", "secondary_color": "#3B82F6"},
|
||||
},
|
||||
{
|
||||
"code": "loyalty",
|
||||
"name": "Loyalty+",
|
||||
"description": "Customer loyalty program platform for Luxembourg businesses",
|
||||
"domain": "loyalty.lu",
|
||||
"path_prefix": "loyalty",
|
||||
"default_language": "fr",
|
||||
"supported_languages": ["fr", "de", "en"],
|
||||
"settings": {"features": ["points", "rewards", "tiers", "analytics"]},
|
||||
"theme_config": {"primary_color": "#8B5CF6", "secondary_color": "#A78BFA"},
|
||||
},
|
||||
]
|
||||
|
||||
platforms = []
|
||||
for pdef in platform_defs:
|
||||
existing = db.execute(
|
||||
select(Platform).where(Platform.code == pdef["code"])
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
print_warning(f"Platform already exists: {existing.name} ({existing.code})")
|
||||
platforms.append(existing)
|
||||
continue
|
||||
|
||||
platform = Platform(
|
||||
code=pdef["code"],
|
||||
name=pdef["name"],
|
||||
description=pdef["description"],
|
||||
domain=pdef["domain"],
|
||||
path_prefix=pdef["path_prefix"],
|
||||
default_language=pdef["default_language"],
|
||||
supported_languages=pdef["supported_languages"],
|
||||
settings=pdef["settings"],
|
||||
theme_config=pdef["theme_config"],
|
||||
is_active=True,
|
||||
is_public=True,
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
db.add(platform)
|
||||
db.flush()
|
||||
platforms.append(platform)
|
||||
print_success(f"Created platform: {platform.name} ({platform.code})")
|
||||
|
||||
return platforms
|
||||
|
||||
|
||||
def create_default_role_templates(db: Session) -> dict:
|
||||
"""Create default role templates (not tied to any store).
|
||||
|
||||
These serve as templates that can be copied when creating store-specific roles.
|
||||
Note: Actual roles are store-specific and created when stores are onboarded.
|
||||
"""
|
||||
|
||||
print(" Available role presets:")
|
||||
print(" - Manager: Nearly full access (except team management)")
|
||||
print(" - Staff: Day-to-day operations")
|
||||
print(" - Support: Customer service focused")
|
||||
print(" - Viewer: Read-only access")
|
||||
print(" - Marketing: Marketing and customer communication")
|
||||
|
||||
print_success("Role templates ready for store onboarding")
|
||||
|
||||
return {
|
||||
"manager": list(permission_discovery_service.get_preset_permissions("manager")),
|
||||
"staff": list(permission_discovery_service.get_preset_permissions("staff")),
|
||||
"support": list(permission_discovery_service.get_preset_permissions("support")),
|
||||
"viewer": list(permission_discovery_service.get_preset_permissions("viewer")),
|
||||
"marketing": list(permission_discovery_service.get_preset_permissions("marketing")),
|
||||
}
|
||||
|
||||
|
||||
def create_admin_settings(db: Session) -> int:
|
||||
"""Create essential admin settings."""
|
||||
|
||||
settings_created = 0
|
||||
|
||||
# Essential platform settings
|
||||
default_settings = [
|
||||
{
|
||||
"key": "platform_name",
|
||||
"value": settings.project_name,
|
||||
"value_type": "string",
|
||||
"description": "Platform name displayed in admin panel",
|
||||
"is_public": True,
|
||||
},
|
||||
{
|
||||
"key": "platform_url",
|
||||
"value": f"https://{settings.platform_domain}",
|
||||
"value_type": "string",
|
||||
"description": "Main platform URL",
|
||||
"is_public": True,
|
||||
},
|
||||
{
|
||||
"key": "support_email",
|
||||
"value": f"support@{settings.platform_domain}",
|
||||
"value_type": "string",
|
||||
"description": "Platform support email",
|
||||
"is_public": True,
|
||||
},
|
||||
{
|
||||
"key": "max_stores_per_user",
|
||||
"value": str(settings.max_stores_per_user),
|
||||
"value_type": "integer",
|
||||
"description": "Maximum stores a user can own",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "max_team_members_per_store",
|
||||
"value": str(settings.max_team_members_per_store),
|
||||
"value_type": "integer",
|
||||
"description": "Maximum team members per store",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "invitation_expiry_days",
|
||||
"value": str(settings.invitation_expiry_days),
|
||||
"value_type": "integer",
|
||||
"description": "Days until team invitation expires",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "require_store_verification",
|
||||
"value": "true",
|
||||
"value_type": "boolean",
|
||||
"description": "Require admin verification for new stores",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "allow_custom_domains",
|
||||
"value": str(settings.allow_custom_domains).lower(),
|
||||
"value_type": "boolean",
|
||||
"description": "Allow stores to use custom domains",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "maintenance_mode",
|
||||
"value": "false",
|
||||
"value_type": "boolean",
|
||||
"description": "Enable maintenance mode",
|
||||
"is_public": True,
|
||||
},
|
||||
# Logging settings
|
||||
{
|
||||
"key": "log_level",
|
||||
"value": "INFO",
|
||||
"value_type": "string",
|
||||
"category": "logging",
|
||||
"description": "Application log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "log_file_max_size_mb",
|
||||
"value": "10",
|
||||
"value_type": "integer",
|
||||
"category": "logging",
|
||||
"description": "Maximum log file size in MB before rotation",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "log_file_backup_count",
|
||||
"value": "5",
|
||||
"value_type": "integer",
|
||||
"category": "logging",
|
||||
"description": "Number of rotated log files to keep",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "db_log_retention_days",
|
||||
"value": "30",
|
||||
"value_type": "integer",
|
||||
"category": "logging",
|
||||
"description": "Number of days to retain logs in database",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "file_logging_enabled",
|
||||
"value": "true",
|
||||
"value_type": "boolean",
|
||||
"category": "logging",
|
||||
"description": "Enable file-based logging",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "db_logging_enabled",
|
||||
"value": "true",
|
||||
"value_type": "boolean",
|
||||
"category": "logging",
|
||||
"description": "Enable database logging for critical events",
|
||||
"is_public": False,
|
||||
},
|
||||
]
|
||||
|
||||
for setting_data in default_settings:
|
||||
# Check if setting already exists
|
||||
existing = db.execute(
|
||||
select(AdminSetting).where(AdminSetting.key == setting_data["key"])
|
||||
).scalar_one_or_none()
|
||||
|
||||
if not existing:
|
||||
setting = AdminSetting(
|
||||
key=setting_data["key"],
|
||||
value=setting_data["value"],
|
||||
value_type=setting_data["value_type"],
|
||||
category=setting_data.get("category"),
|
||||
description=setting_data.get("description"),
|
||||
is_public=setting_data.get("is_public", False),
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
db.add(setting)
|
||||
settings_created += 1
|
||||
|
||||
db.flush()
|
||||
|
||||
if settings_created > 0:
|
||||
print_success(f"Created {settings_created} admin settings")
|
||||
else:
|
||||
print_warning("Admin settings already exist")
|
||||
|
||||
return settings_created
|
||||
|
||||
|
||||
def create_subscription_tiers(db: Session, platform: Platform) -> int:
|
||||
"""Create default subscription tiers for the OMS platform."""
|
||||
|
||||
tier_defs = [
|
||||
{
|
||||
"code": "essential",
|
||||
"name": "Essential",
|
||||
"price_monthly_cents": 2900,
|
||||
"price_annual_cents": 29000,
|
||||
"is_public": True,
|
||||
"display_order": 10,
|
||||
},
|
||||
{
|
||||
"code": "professional",
|
||||
"name": "Professional",
|
||||
"price_monthly_cents": 7900,
|
||||
"price_annual_cents": 79000,
|
||||
"is_public": True,
|
||||
"display_order": 20,
|
||||
},
|
||||
{
|
||||
"code": "business",
|
||||
"name": "Business",
|
||||
"price_monthly_cents": 14900,
|
||||
"price_annual_cents": 149000,
|
||||
"is_public": True,
|
||||
"display_order": 30,
|
||||
},
|
||||
{
|
||||
"code": "enterprise",
|
||||
"name": "Enterprise",
|
||||
"price_monthly_cents": 29900,
|
||||
"price_annual_cents": None,
|
||||
"is_public": False,
|
||||
"display_order": 40,
|
||||
},
|
||||
]
|
||||
|
||||
tiers_created = 0
|
||||
for tdef in tier_defs:
|
||||
existing = db.execute(
|
||||
select(SubscriptionTier).where(SubscriptionTier.code == tdef["code"])
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
print_warning(f"Tier already exists: {existing.name} ({existing.code})")
|
||||
continue
|
||||
|
||||
tier = SubscriptionTier(
|
||||
platform_id=platform.id,
|
||||
code=tdef["code"],
|
||||
name=tdef["name"],
|
||||
price_monthly_cents=tdef["price_monthly_cents"],
|
||||
price_annual_cents=tdef["price_annual_cents"],
|
||||
is_public=tdef["is_public"],
|
||||
display_order=tdef["display_order"],
|
||||
is_active=True,
|
||||
)
|
||||
db.add(tier)
|
||||
db.flush()
|
||||
tiers_created += 1
|
||||
print_success(f"Created tier: {tier.name} ({tier.code})")
|
||||
|
||||
return tiers_created
|
||||
|
||||
|
||||
def verify_rbac_schema(db: Session) -> bool:
|
||||
"""Verify that RBAC schema is in place."""
|
||||
|
||||
try:
|
||||
from sqlalchemy import inspect
|
||||
|
||||
inspector = inspect(db.bind)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
# Check users table has is_email_verified
|
||||
if "users" in tables:
|
||||
user_cols = {col["name"] for col in inspector.get_columns("users")}
|
||||
if "is_email_verified" not in user_cols:
|
||||
print_error("Missing 'is_email_verified' column in users table")
|
||||
return False
|
||||
|
||||
# Check store_users has RBAC columns
|
||||
if "store_users" in tables:
|
||||
vu_cols = {col["name"] for col in inspector.get_columns("store_users")}
|
||||
required_cols = {
|
||||
"user_type",
|
||||
"invitation_token",
|
||||
"invitation_sent_at",
|
||||
"invitation_accepted_at",
|
||||
}
|
||||
missing = required_cols - vu_cols
|
||||
if missing:
|
||||
print_error(f"Missing columns in store_users: {missing}")
|
||||
return False
|
||||
|
||||
# Check roles table exists
|
||||
if "roles" not in tables:
|
||||
print_error("Missing 'roles' table")
|
||||
return False
|
||||
|
||||
print_success("RBAC schema verified")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print_error(f"Schema verification failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MAIN INITIALIZATION
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def initialize_production(db: Session, auth_manager: AuthManager):
|
||||
"""Initialize production database with essential data."""
|
||||
|
||||
print_header("PRODUCTION INITIALIZATION")
|
||||
|
||||
# Step 1: Verify RBAC schema
|
||||
print_step(1, "Verifying RBAC schema...")
|
||||
if not verify_rbac_schema(db):
|
||||
print_error("RBAC schema not ready. Run migrations first:")
|
||||
print(" make migrate-up")
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Create admin user
|
||||
print_step(2, "Creating platform admin...")
|
||||
admin = create_admin_user(db, auth_manager)
|
||||
|
||||
# Step 3: Create default platforms
|
||||
print_step(3, "Creating default platforms...")
|
||||
platforms = create_default_platforms(db)
|
||||
|
||||
# Step 4: Set up default role templates
|
||||
print_step(4, "Setting up role templates...")
|
||||
role_templates = create_default_role_templates(db)
|
||||
|
||||
# Step 5: Create admin settings
|
||||
print_step(5, "Creating admin settings...")
|
||||
create_admin_settings(db)
|
||||
|
||||
# Step 6: Seed subscription tiers
|
||||
print_step(6, "Seeding subscription tiers...")
|
||||
oms_platform = next((p for p in platforms if p.code == "oms"), None)
|
||||
if oms_platform:
|
||||
create_subscription_tiers(db, oms_platform)
|
||||
else:
|
||||
print_warning("OMS platform not found, skipping tier seeding")
|
||||
|
||||
# Commit all changes
|
||||
db.commit()
|
||||
print_success("All changes committed")
|
||||
|
||||
|
||||
def print_summary(db: Session):
|
||||
"""Print initialization summary."""
|
||||
|
||||
print_header("INITIALIZATION SUMMARY")
|
||||
|
||||
# Count records
|
||||
user_count = db.query(User).filter(User.role == "admin").count()
|
||||
setting_count = db.query(AdminSetting).count()
|
||||
platform_count = db.query(Platform).count()
|
||||
tier_count = db.query(SubscriptionTier).filter(SubscriptionTier.is_active.is_(True)).count()
|
||||
|
||||
print("\n📊 Database Status:")
|
||||
print(f" Admin users: {user_count}")
|
||||
print(f" Platforms: {platform_count}")
|
||||
print(f" Admin settings: {setting_count}")
|
||||
print(f" Sub. tiers: {tier_count}")
|
||||
|
||||
print("\n" + "─" * 70)
|
||||
print("🔐 ADMIN CREDENTIALS")
|
||||
print("─" * 70)
|
||||
print(" URL: /admin/login")
|
||||
print(f" Username: {settings.admin_username}")
|
||||
print(f" Password: {settings.admin_password}")
|
||||
print("─" * 70)
|
||||
|
||||
# Show security warnings if in production
|
||||
if is_production():
|
||||
warnings = validate_production_settings()
|
||||
if warnings:
|
||||
print("\n⚠️ SECURITY WARNINGS:")
|
||||
for warning in warnings:
|
||||
print(f" {warning}")
|
||||
print("\n Please update your .env file with secure values!")
|
||||
else:
|
||||
print("\n⚠️ DEVELOPMENT MODE:")
|
||||
print(" Default credentials are OK for development")
|
||||
print(" Change them in production via .env file")
|
||||
|
||||
print("\n🚀 NEXT STEPS:")
|
||||
print(" 1. Login to admin panel")
|
||||
if is_production():
|
||||
print(" 2. CHANGE DEFAULT PASSWORD IMMEDIATELY!")
|
||||
print(" 3. Configure admin settings")
|
||||
print(" 4. Create first store")
|
||||
else:
|
||||
print(" 2. Create demo data: make seed-demo")
|
||||
print(" 3. Start development: make dev")
|
||||
|
||||
print("\n📝 FOR DEMO DATA (Development only):")
|
||||
print(" make seed-demo")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MAIN ENTRY POINT
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
|
||||
print("\n" + "╔" + "═" * 68 + "╗")
|
||||
print("║" + " " * 16 + "PRODUCTION INITIALIZATION" + " " * 27 + "║")
|
||||
print("╚" + "═" * 68 + "╝")
|
||||
|
||||
# Show environment info
|
||||
print_environment_info()
|
||||
|
||||
# Production safety check
|
||||
if is_production():
|
||||
warnings = validate_production_settings()
|
||||
if warnings:
|
||||
print("\n⚠️ PRODUCTION WARNINGS DETECTED:")
|
||||
for warning in warnings:
|
||||
print(f" {warning}")
|
||||
print("\nUpdate your .env file before continuing!")
|
||||
|
||||
response = input("\nContinue anyway? (yes/no): ")
|
||||
if response.lower() != "yes":
|
||||
print("Initialization cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
db = SessionLocal()
|
||||
auth_manager = AuthManager()
|
||||
|
||||
try:
|
||||
initialize_production(db, auth_manager)
|
||||
print_summary(db)
|
||||
|
||||
print_header("✅ INITIALIZATION COMPLETED")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
db.rollback()
|
||||
print("\n\n⚠️ Initialization interrupted")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print_header("❌ INITIALIZATION FAILED")
|
||||
print(f"\nError: {e}\n")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
595
scripts/seed/install.py
Executable file
595
scripts/seed/install.py
Executable file
@@ -0,0 +1,595 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Wizamart Platform Installation Script
|
||||
|
||||
This script handles first-time installation of the Wizamart platform:
|
||||
1. Validates Python version and dependencies
|
||||
2. Checks environment configuration (.env file)
|
||||
3. Validates required settings for production
|
||||
4. Runs database migrations
|
||||
5. Initializes essential data (admin, settings, CMS, email templates)
|
||||
6. Provides a configuration status report
|
||||
|
||||
Usage:
|
||||
make platform-install
|
||||
# or directly:
|
||||
python scripts/install.py
|
||||
|
||||
This script is idempotent - safe to run multiple times.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CONSOLE OUTPUT HELPERS
|
||||
# =============================================================================
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output."""
|
||||
HEADER = "\033[95m"
|
||||
BLUE = "\033[94m"
|
||||
CYAN = "\033[96m"
|
||||
GREEN = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
FAIL = "\033[91m"
|
||||
ENDC = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
DIM = "\033[2m"
|
||||
|
||||
|
||||
def print_header(text: str):
|
||||
"""Print a bold header."""
|
||||
print(f"\n{Colors.BOLD}{Colors.HEADER}{'=' * 70}{Colors.ENDC}")
|
||||
print(f"{Colors.BOLD}{Colors.HEADER} {text}{Colors.ENDC}")
|
||||
print(f"{Colors.BOLD}{Colors.HEADER}{'=' * 70}{Colors.ENDC}")
|
||||
|
||||
|
||||
def print_section(text: str):
|
||||
"""Print a section header."""
|
||||
print(f"\n{Colors.BOLD}{Colors.CYAN}[*] {text}{Colors.ENDC}")
|
||||
|
||||
|
||||
def print_step(step: int, total: int, text: str):
|
||||
"""Print a step indicator."""
|
||||
print(f"\n{Colors.BLUE}[{step}/{total}] {text}{Colors.ENDC}")
|
||||
|
||||
|
||||
def print_success(text: str):
|
||||
"""Print success message."""
|
||||
print(f" {Colors.GREEN}✓{Colors.ENDC} {text}")
|
||||
|
||||
|
||||
def print_warning(text: str):
|
||||
"""Print warning message."""
|
||||
print(f" {Colors.WARNING}⚠{Colors.ENDC} {text}")
|
||||
|
||||
|
||||
def print_error(text: str):
|
||||
"""Print error message."""
|
||||
print(f" {Colors.FAIL}✗{Colors.ENDC} {text}")
|
||||
|
||||
|
||||
def print_info(text: str):
|
||||
"""Print info message."""
|
||||
print(f" {Colors.DIM}→{Colors.ENDC} {text}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# VALIDATION FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
def check_python_version() -> bool:
|
||||
"""Check if Python version is supported."""
|
||||
major, minor = sys.version_info[:2]
|
||||
if major < 3 or (major == 3 and minor < 11):
|
||||
print_error(f"Python 3.11+ required. Found: {major}.{minor}")
|
||||
return False
|
||||
print_success(f"Python version: {major}.{minor}")
|
||||
return True
|
||||
|
||||
|
||||
def check_env_file() -> tuple[bool, dict]:
|
||||
"""Check if .env file exists and load it."""
|
||||
env_path = project_root / ".env"
|
||||
env_example_path = project_root / ".env.example"
|
||||
|
||||
if not env_path.exists():
|
||||
if env_example_path.exists():
|
||||
print_warning(".env file not found")
|
||||
print_info("Copy .env.example to .env and configure it:")
|
||||
print_info(" cp .env.example .env")
|
||||
return False, {}
|
||||
else:
|
||||
print_warning("Neither .env nor .env.example found")
|
||||
return False, {}
|
||||
|
||||
print_success(".env file found")
|
||||
|
||||
# Load .env manually to check values
|
||||
env_vars = {}
|
||||
with open(env_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, _, value = line.partition("=")
|
||||
# Remove quotes if present
|
||||
value = value.strip().strip("'\"")
|
||||
env_vars[key.strip()] = value
|
||||
|
||||
return True, env_vars
|
||||
|
||||
|
||||
def validate_configuration(env_vars: dict) -> dict:
|
||||
"""
|
||||
Validate configuration and return status for each category.
|
||||
|
||||
Returns dict with categories and their status:
|
||||
{
|
||||
"category": {
|
||||
"status": "ok" | "warning" | "missing",
|
||||
"message": "...",
|
||||
"items": [...]
|
||||
}
|
||||
}
|
||||
"""
|
||||
results = {}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Database
|
||||
# -------------------------------------------------------------------------
|
||||
db_url = env_vars.get("DATABASE_URL", "")
|
||||
if db_url and "sqlite" not in db_url.lower():
|
||||
results["database"] = {
|
||||
"status": "ok",
|
||||
"message": "Production database configured",
|
||||
"items": [f"URL: {db_url[:50]}..."]
|
||||
}
|
||||
elif db_url and "sqlite" in db_url.lower():
|
||||
results["database"] = {
|
||||
"status": "warning",
|
||||
"message": "SQLite database (OK for development)",
|
||||
"items": ["Consider PostgreSQL for production"]
|
||||
}
|
||||
else:
|
||||
results["database"] = {
|
||||
"status": "ok",
|
||||
"message": "Using default SQLite database",
|
||||
"items": ["Configure DATABASE_URL for production"]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Security (JWT)
|
||||
# -------------------------------------------------------------------------
|
||||
jwt_key = env_vars.get("JWT_SECRET_KEY", "")
|
||||
if jwt_key and jwt_key != "change-this-in-production" and len(jwt_key) >= 32:
|
||||
results["security"] = {
|
||||
"status": "ok",
|
||||
"message": "JWT secret configured",
|
||||
"items": ["Secret key is set and sufficiently long"]
|
||||
}
|
||||
elif jwt_key and jwt_key == "change-this-in-production":
|
||||
results["security"] = {
|
||||
"status": "warning",
|
||||
"message": "Using default JWT secret",
|
||||
"items": [
|
||||
"CRITICAL: Change JWT_SECRET_KEY for production!",
|
||||
"Use: openssl rand -hex 32"
|
||||
]
|
||||
}
|
||||
else:
|
||||
results["security"] = {
|
||||
"status": "warning",
|
||||
"message": "JWT secret not explicitly set",
|
||||
"items": ["Set JWT_SECRET_KEY in .env"]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Admin Credentials
|
||||
# -------------------------------------------------------------------------
|
||||
admin_pass = env_vars.get("ADMIN_PASSWORD", "admin123")
|
||||
admin_email = env_vars.get("ADMIN_EMAIL", "admin@wizamart.com")
|
||||
if admin_pass != "admin123":
|
||||
results["admin"] = {
|
||||
"status": "ok",
|
||||
"message": "Admin credentials configured",
|
||||
"items": [f"Email: {admin_email}"]
|
||||
}
|
||||
else:
|
||||
results["admin"] = {
|
||||
"status": "warning",
|
||||
"message": "Using default admin password",
|
||||
"items": [
|
||||
"Set ADMIN_PASSWORD in .env",
|
||||
"Change immediately after first login"
|
||||
]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Stripe Billing
|
||||
# -------------------------------------------------------------------------
|
||||
stripe_secret = env_vars.get("STRIPE_SECRET_KEY", "")
|
||||
stripe_pub = env_vars.get("STRIPE_PUBLISHABLE_KEY", "")
|
||||
stripe_webhook = env_vars.get("STRIPE_WEBHOOK_SECRET", "")
|
||||
|
||||
if stripe_secret and stripe_pub:
|
||||
if stripe_secret.startswith("sk_live_"):
|
||||
results["stripe"] = {
|
||||
"status": "ok",
|
||||
"message": "Stripe LIVE mode configured",
|
||||
"items": [
|
||||
"Live secret key set",
|
||||
f"Webhook secret: {'configured' if stripe_webhook else 'NOT SET'}"
|
||||
]
|
||||
}
|
||||
elif stripe_secret.startswith("sk_test_"):
|
||||
results["stripe"] = {
|
||||
"status": "warning",
|
||||
"message": "Stripe TEST mode configured",
|
||||
"items": [
|
||||
"Using test keys (OK for development)",
|
||||
"Switch to live keys for production"
|
||||
]
|
||||
}
|
||||
else:
|
||||
results["stripe"] = {
|
||||
"status": "warning",
|
||||
"message": "Stripe keys set but format unclear",
|
||||
"items": ["Verify STRIPE_SECRET_KEY format"]
|
||||
}
|
||||
else:
|
||||
results["stripe"] = {
|
||||
"status": "missing",
|
||||
"message": "Stripe not configured",
|
||||
"items": [
|
||||
"Set STRIPE_SECRET_KEY",
|
||||
"Set STRIPE_PUBLISHABLE_KEY",
|
||||
"Set STRIPE_WEBHOOK_SECRET",
|
||||
"Billing features will not work!"
|
||||
]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Email Configuration
|
||||
# -------------------------------------------------------------------------
|
||||
email_provider = env_vars.get("EMAIL_PROVIDER", "smtp")
|
||||
email_enabled = env_vars.get("EMAIL_ENABLED", "true").lower() == "true"
|
||||
email_debug = env_vars.get("EMAIL_DEBUG", "false").lower() == "true"
|
||||
|
||||
if not email_enabled:
|
||||
results["email"] = {
|
||||
"status": "warning",
|
||||
"message": "Email disabled",
|
||||
"items": ["EMAIL_ENABLED=false - no emails will be sent"]
|
||||
}
|
||||
elif email_debug:
|
||||
results["email"] = {
|
||||
"status": "warning",
|
||||
"message": "Email in debug mode",
|
||||
"items": ["EMAIL_DEBUG=true - emails logged, not sent"]
|
||||
}
|
||||
elif email_provider == "smtp":
|
||||
smtp_host = env_vars.get("SMTP_HOST", "localhost")
|
||||
smtp_user = env_vars.get("SMTP_USER", "")
|
||||
if smtp_host != "localhost" and smtp_user:
|
||||
results["email"] = {
|
||||
"status": "ok",
|
||||
"message": f"SMTP configured ({smtp_host})",
|
||||
"items": [f"User: {smtp_user}"]
|
||||
}
|
||||
else:
|
||||
results["email"] = {
|
||||
"status": "warning",
|
||||
"message": "SMTP using defaults",
|
||||
"items": [
|
||||
"Configure SMTP_HOST, SMTP_USER, SMTP_PASSWORD",
|
||||
"Or use EMAIL_DEBUG=true for development"
|
||||
]
|
||||
}
|
||||
elif email_provider == "sendgrid":
|
||||
api_key = env_vars.get("SENDGRID_API_KEY", "")
|
||||
if api_key:
|
||||
results["email"] = {
|
||||
"status": "ok",
|
||||
"message": "SendGrid configured",
|
||||
"items": ["API key set"]
|
||||
}
|
||||
else:
|
||||
results["email"] = {
|
||||
"status": "missing",
|
||||
"message": "SendGrid selected but not configured",
|
||||
"items": ["Set SENDGRID_API_KEY"]
|
||||
}
|
||||
elif email_provider == "mailgun":
|
||||
api_key = env_vars.get("MAILGUN_API_KEY", "")
|
||||
domain = env_vars.get("MAILGUN_DOMAIN", "")
|
||||
if api_key and domain:
|
||||
results["email"] = {
|
||||
"status": "ok",
|
||||
"message": f"Mailgun configured ({domain})",
|
||||
"items": ["API key and domain set"]
|
||||
}
|
||||
else:
|
||||
results["email"] = {
|
||||
"status": "missing",
|
||||
"message": "Mailgun selected but not configured",
|
||||
"items": ["Set MAILGUN_API_KEY and MAILGUN_DOMAIN"]
|
||||
}
|
||||
elif email_provider == "ses":
|
||||
access_key = env_vars.get("AWS_ACCESS_KEY_ID", "")
|
||||
secret_key = env_vars.get("AWS_SECRET_ACCESS_KEY", "")
|
||||
if access_key and secret_key:
|
||||
results["email"] = {
|
||||
"status": "ok",
|
||||
"message": "Amazon SES configured",
|
||||
"items": [f"Region: {env_vars.get('AWS_REGION', 'eu-west-1')}"]
|
||||
}
|
||||
else:
|
||||
results["email"] = {
|
||||
"status": "missing",
|
||||
"message": "SES selected but not configured",
|
||||
"items": ["Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY"]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Platform Domain
|
||||
# -------------------------------------------------------------------------
|
||||
domain = env_vars.get("PLATFORM_DOMAIN", "wizamart.com")
|
||||
if domain != "wizamart.com":
|
||||
results["domain"] = {
|
||||
"status": "ok",
|
||||
"message": f"Custom domain: {domain}",
|
||||
"items": []
|
||||
}
|
||||
else:
|
||||
results["domain"] = {
|
||||
"status": "warning",
|
||||
"message": "Using default domain",
|
||||
"items": ["Set PLATFORM_DOMAIN for your deployment"]
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Celery / Redis Task Queue
|
||||
# -------------------------------------------------------------------------
|
||||
redis_url = env_vars.get("REDIS_URL", "")
|
||||
use_celery = env_vars.get("USE_CELERY", "false").lower() == "true"
|
||||
flower_url = env_vars.get("FLOWER_URL", "")
|
||||
flower_password = env_vars.get("FLOWER_PASSWORD", "")
|
||||
|
||||
if use_celery:
|
||||
if redis_url:
|
||||
celery_items = [f"Redis: {redis_url.split('@')[-1] if '@' in redis_url else redis_url}"]
|
||||
|
||||
if flower_url:
|
||||
celery_items.append(f"Flower: {flower_url}")
|
||||
else:
|
||||
celery_items.append("FLOWER_URL not set (monitoring disabled)")
|
||||
|
||||
if flower_password and flower_password != "changeme":
|
||||
celery_items.append("Flower password configured")
|
||||
elif flower_password == "changeme":
|
||||
celery_items.append("WARNING: Change FLOWER_PASSWORD for production!")
|
||||
|
||||
results["celery"] = {
|
||||
"status": "ok",
|
||||
"message": "Celery enabled with Redis",
|
||||
"items": celery_items
|
||||
}
|
||||
else:
|
||||
results["celery"] = {
|
||||
"status": "missing",
|
||||
"message": "Celery enabled but Redis not configured",
|
||||
"items": [
|
||||
"Set REDIS_URL (e.g., redis://localhost:6379/0)",
|
||||
"Or disable Celery: USE_CELERY=false"
|
||||
]
|
||||
}
|
||||
else:
|
||||
results["celery"] = {
|
||||
"status": "warning",
|
||||
"message": "Celery disabled (using FastAPI BackgroundTasks)",
|
||||
"items": [
|
||||
"Set USE_CELERY=true for production",
|
||||
"Requires Redis: docker-compose up -d redis"
|
||||
]
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def print_configuration_report(config_status: dict):
|
||||
"""Print a formatted configuration status report."""
|
||||
print_section("Configuration Status")
|
||||
|
||||
ok_count = 0
|
||||
warning_count = 0
|
||||
missing_count = 0
|
||||
|
||||
for category, status in config_status.items():
|
||||
if status["status"] == "ok":
|
||||
icon = f"{Colors.GREEN}✓{Colors.ENDC}"
|
||||
ok_count += 1
|
||||
elif status["status"] == "warning":
|
||||
icon = f"{Colors.WARNING}⚠{Colors.ENDC}"
|
||||
warning_count += 1
|
||||
else:
|
||||
icon = f"{Colors.FAIL}✗{Colors.ENDC}"
|
||||
missing_count += 1
|
||||
|
||||
print(f"\n {icon} {Colors.BOLD}{category.upper()}{Colors.ENDC}: {status['message']}")
|
||||
for item in status.get("items", []):
|
||||
print(f" {Colors.DIM}→ {item}{Colors.ENDC}")
|
||||
|
||||
print(f"\n {Colors.DIM}─" * 35 + Colors.ENDC)
|
||||
print(f" Summary: {Colors.GREEN}{ok_count} OK{Colors.ENDC}, "
|
||||
f"{Colors.WARNING}{warning_count} warnings{Colors.ENDC}, "
|
||||
f"{Colors.FAIL}{missing_count} missing{Colors.ENDC}")
|
||||
|
||||
return ok_count, warning_count, missing_count
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# INSTALLATION STEPS
|
||||
# =============================================================================
|
||||
|
||||
def run_migrations() -> bool:
|
||||
"""Run database migrations."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "alembic", "upgrade", "head"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print_success("Migrations completed successfully")
|
||||
return True
|
||||
else:
|
||||
print_error("Migration failed")
|
||||
if result.stderr:
|
||||
print_info(result.stderr[:500])
|
||||
return False
|
||||
except Exception as e:
|
||||
print_error(f"Failed to run migrations: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def run_init_script(script_name: str, description: str) -> bool:
|
||||
"""Run an initialization script."""
|
||||
script_path = project_root / "scripts" / script_name
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(script_path)],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print_success(description)
|
||||
return True
|
||||
else:
|
||||
print_error(f"Failed: {description}")
|
||||
if result.stderr:
|
||||
print_info(result.stderr[:300])
|
||||
return False
|
||||
except Exception as e:
|
||||
print_error(f"Error running {script_name}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MAIN INSTALLATION FLOW
|
||||
# =============================================================================
|
||||
|
||||
def main():
|
||||
"""Main installation flow."""
|
||||
print_header("WIZAMART PLATFORM INSTALLATION")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Step 1: Pre-flight checks
|
||||
# -------------------------------------------------------------------------
|
||||
print_step(1, 4, "Pre-flight checks")
|
||||
|
||||
if not check_python_version():
|
||||
sys.exit(1)
|
||||
|
||||
env_exists, env_vars = check_env_file()
|
||||
if not env_exists:
|
||||
print()
|
||||
print_warning("Installation can continue with defaults,")
|
||||
print_warning("but you should configure .env before going to production.")
|
||||
print()
|
||||
response = input("Continue with default configuration? [y/N]: ")
|
||||
if response.lower() != "y":
|
||||
print("\nInstallation cancelled. Please configure .env first.")
|
||||
sys.exit(0)
|
||||
env_vars = {}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Step 2: Validate configuration
|
||||
# -------------------------------------------------------------------------
|
||||
print_step(2, 4, "Validating configuration")
|
||||
|
||||
config_status = validate_configuration(env_vars)
|
||||
ok_count, warning_count, missing_count = print_configuration_report(config_status)
|
||||
|
||||
if missing_count > 0:
|
||||
print()
|
||||
print_warning(f"{missing_count} critical configuration(s) missing!")
|
||||
print_warning("The platform may not function correctly.")
|
||||
response = input("\nContinue anyway? [y/N]: ")
|
||||
if response.lower() != "y":
|
||||
print("\nInstallation cancelled. Please fix configuration first.")
|
||||
sys.exit(0)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Step 3: Database setup
|
||||
# -------------------------------------------------------------------------
|
||||
print_step(3, 4, "Database setup")
|
||||
|
||||
print_info("Running database migrations...")
|
||||
if not run_migrations():
|
||||
print_error("Failed to run migrations. Cannot continue.")
|
||||
sys.exit(1)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Step 4: Initialize platform data
|
||||
# -------------------------------------------------------------------------
|
||||
print_step(4, 4, "Initializing platform data")
|
||||
|
||||
init_scripts = [
|
||||
("init_production.py", "Admin user and platform settings"),
|
||||
("init_log_settings.py", "Log settings"),
|
||||
("create_default_content_pages.py", "Default CMS pages"),
|
||||
("create_platform_pages.py", "Platform pages and landing"),
|
||||
("seed_email_templates.py", "Email templates"),
|
||||
]
|
||||
|
||||
all_success = True
|
||||
for script, description in init_scripts:
|
||||
if not run_init_script(script, description):
|
||||
all_success = False
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Summary
|
||||
# -------------------------------------------------------------------------
|
||||
print_header("INSTALLATION COMPLETE")
|
||||
|
||||
if all_success:
|
||||
print(f"\n {Colors.GREEN}✓ Platform installed successfully!{Colors.ENDC}")
|
||||
else:
|
||||
print(f"\n {Colors.WARNING}⚠ Installation completed with some errors{Colors.ENDC}")
|
||||
|
||||
print(f"\n {Colors.BOLD}Next Steps:{Colors.ENDC}")
|
||||
print(" 1. Review any warnings above")
|
||||
|
||||
if warning_count > 0 or missing_count > 0:
|
||||
print(" 2. Update .env with production values")
|
||||
print(" 3. Run 'make platform-install' again to verify")
|
||||
else:
|
||||
print(" 2. Start the application: make dev")
|
||||
|
||||
print(f"\n {Colors.BOLD}Admin Login:{Colors.ENDC}")
|
||||
admin_email = env_vars.get("ADMIN_EMAIL", "admin@wizamart.com")
|
||||
print(f" URL: /admin/login")
|
||||
print(f" Email: {admin_email}")
|
||||
print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}")
|
||||
|
||||
if not env_vars.get("ADMIN_PASSWORD"):
|
||||
print(f"\n {Colors.WARNING}⚠ CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}")
|
||||
|
||||
print(f"\n {Colors.BOLD}For demo data (development only):{Colors.ENDC}")
|
||||
print(" make seed-demo")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1267
scripts/seed/seed_demo.py
Normal file
1267
scripts/seed/seed_demo.py
Normal file
File diff suppressed because it is too large
Load Diff
1329
scripts/seed/seed_email_templates.py
Normal file
1329
scripts/seed/seed_email_templates.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user