data seed feature for demo and prod
This commit is contained in:
109
.env
109
.env
@@ -1,53 +1,104 @@
|
||||
# .env.example
|
||||
# =============================================================================
|
||||
# ENVIRONMENT CONFIGURATION
|
||||
# =============================================================================
|
||||
DEBUG=True
|
||||
|
||||
# Project information
|
||||
PROJECT_NAME=Ecommerce Backend API with Marketplace Support
|
||||
DESCRIPTION=Advanced product management system with JWT authentication
|
||||
VERSION=0.0.1
|
||||
# =============================================================================
|
||||
# PROJECT INFORMATION
|
||||
# =============================================================================
|
||||
PROJECT_NAME=Wizamart - Multi-Vendor Marketplace Platform
|
||||
DESCRIPTION=Multi-tenants multi-themes ecommerce application
|
||||
VERSION=2.2.0
|
||||
|
||||
# Database Configuration
|
||||
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
|
||||
# For development, you can use SQLite:
|
||||
# =============================================================================
|
||||
# DATABASE CONFIGURATION
|
||||
# =============================================================================
|
||||
# For development (SQLite)
|
||||
DATABASE_URL=sqlite:///./wizamart.db
|
||||
|
||||
# Documentation
|
||||
# .env.development
|
||||
DOCUMENTATION_URL=http://localhost:8001
|
||||
# .env.production
|
||||
# DOCUMENTATION_URL=https://yourdomain.com/docs
|
||||
# .env.staging
|
||||
# DOCUMENTATION_URL=https://staging-docs.yourdomain.com
|
||||
# For production (PostgreSQL)
|
||||
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
|
||||
|
||||
# JWT Configuration
|
||||
# =============================================================================
|
||||
# ADMIN INITIALIZATION
|
||||
# =============================================================================
|
||||
# These are used by init_production.py to create the platform admin
|
||||
# ⚠️ CHANGE THESE IN PRODUCTION!
|
||||
ADMIN_EMAIL=admin@wizamart.com
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
ADMIN_FIRST_NAME=Platform
|
||||
ADMIN_LAST_NAME=Administrator
|
||||
|
||||
# =============================================================================
|
||||
# JWT CONFIGURATION
|
||||
# =============================================================================
|
||||
JWT_SECRET_KEY=your-super-secret-jwt-key-change-in-production
|
||||
JWT_EXPIRE_HOURS=24
|
||||
JWT_EXPIRE_MINUTES=30
|
||||
|
||||
# API Configuration
|
||||
# =============================================================================
|
||||
# API SERVER
|
||||
# =============================================================================
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=8000
|
||||
DEBUG=False
|
||||
|
||||
# Rate Limiting
|
||||
# =============================================================================
|
||||
# DOCUMENTATION
|
||||
# =============================================================================
|
||||
# Development
|
||||
DOCUMENTATION_URL=http://localhost:8001
|
||||
# Staging
|
||||
# DOCUMENTATION_URL=https://staging-docs.wizamart.com
|
||||
# Production
|
||||
# DOCUMENTATION_URL=https://docs.wizamart.com
|
||||
|
||||
# =============================================================================
|
||||
# RATE LIMITING
|
||||
# =============================================================================
|
||||
RATE_LIMIT_ENABLED=True
|
||||
RATE_LIMIT_REQUESTS=100
|
||||
RATE_LIMIT_WINDOW=3600
|
||||
|
||||
# Logging
|
||||
# =============================================================================
|
||||
# LOGGING
|
||||
# =============================================================================
|
||||
LOG_LEVEL=DEBUG
|
||||
LOG_FILE=log/app.log
|
||||
LOG_FILE=logs/app.log
|
||||
|
||||
# Platform domain configuration
|
||||
PLATFORM_DOMAIN=platform.com # Your main platform domain
|
||||
# =============================================================================
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
# =============================================================================
|
||||
# Your main platform domain
|
||||
PLATFORM_DOMAIN=wizamart.com
|
||||
|
||||
# Custom domain features
|
||||
ALLOW_CUSTOM_DOMAINS=True # Enable/disable custom domains
|
||||
REQUIRE_DOMAIN_VERIFICATION=True # Require DNS verification
|
||||
# Enable/disable custom domains
|
||||
ALLOW_CUSTOM_DOMAINS=True
|
||||
# Require DNS verification
|
||||
REQUIRE_DOMAIN_VERIFICATION=True
|
||||
|
||||
# SSL/TLS configuration for custom domains
|
||||
SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
|
||||
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
||||
# SSL/TLS configuration
|
||||
# "letsencrypt" or "cloudflare", "manual"
|
||||
SSL_PROVIDER=letsencrypt
|
||||
# Set to True if using automated SSL
|
||||
AUTO_PROVISION_SSL=False
|
||||
|
||||
# DNS verification
|
||||
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
||||
DNS_VERIFICATION_TTL=3600
|
||||
DNS_VERIFICATION_TTL=3600
|
||||
|
||||
# =============================================================================
|
||||
# PLATFORM LIMITS
|
||||
# =============================================================================
|
||||
MAX_VENDORS_PER_USER=5
|
||||
MAX_TEAM_MEMBERS_PER_VENDOR=50
|
||||
INVITATION_EXPIRY_DAYS=7
|
||||
|
||||
# =============================================================================
|
||||
# DEMO/SEED DATA CONFIGURATION (Development only)
|
||||
# =============================================================================
|
||||
SEED_DEMO_VENDORS=3
|
||||
SEED_CUSTOMERS_PER_VENDOR=15
|
||||
SEED_PRODUCTS_PER_VENDOR=20
|
||||
SEED_ORDERS_PER_VENDOR=10
|
||||
347
Makefile
347
Makefile
@@ -1,7 +1,16 @@
|
||||
# Multitenant ecommerce application Makefile (Windows Compatible)
|
||||
# Wizamart Multi-Tenant E-Commerce Platform Makefile
|
||||
# Cross-platform compatible (Windows & Linux)
|
||||
|
||||
.PHONY: install install-dev install-docs install-all dev test test-coverage lint format check docker-build docker-up docker-down clean help
|
||||
|
||||
# Detect if we're in a virtual environment and set Python path accordingly
|
||||
# Detect OS
|
||||
ifeq ($(OS),Windows_NT)
|
||||
DETECTED_OS := Windows
|
||||
else
|
||||
DETECTED_OS := $(shell uname -s)
|
||||
endif
|
||||
|
||||
# Set Python based on OS
|
||||
PYTHON := python
|
||||
PIP := pip
|
||||
|
||||
@@ -23,9 +32,9 @@ install-docs:
|
||||
|
||||
install-all: install install-dev install-test install-docs
|
||||
|
||||
setup: install-all migrate-up
|
||||
@echo Development environment setup complete!
|
||||
@echo Run 'make dev' to start development server
|
||||
setup: install-all migrate-up init-prod
|
||||
@echo "✅ Development environment setup complete!"
|
||||
@echo "Run 'make dev' to start development server"
|
||||
|
||||
# =============================================================================
|
||||
# DEVELOPMENT SERVERS
|
||||
@@ -34,78 +43,96 @@ setup: install-all migrate-up
|
||||
dev:
|
||||
$(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
dev-with-docs:
|
||||
@echo Starting API and documentation servers...
|
||||
@start /B $(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
@timeout /t 3 >nul
|
||||
@$(PYTHON) -m mkdocs serve --dev-addr=0.0.0.0:8001
|
||||
|
||||
dev-full: dev-with-docs
|
||||
@echo Development environment ready!
|
||||
@echo API server: http://localhost:8000
|
||||
@echo API docs: http://localhost:8000/docs
|
||||
@echo Documentation: http://localhost:8001
|
||||
|
||||
# =============================================================================
|
||||
# DATABASE MIGRATIONS
|
||||
# =============================================================================
|
||||
|
||||
migrate-create:
|
||||
@if [ "$(message)" = "" ]; then \
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@if "$(message)"=="" (echo Error: Please provide a message. Usage: make migrate-create message="your_description") else ($(PYTHON) -m alembic revision --autogenerate -m "$(message)")
|
||||
else
|
||||
@if [ -z "$(message)" ]; then \
|
||||
echo "Error: Please provide a message. Usage: make migrate-create message=\"your_description\""; \
|
||||
else \
|
||||
$(PYTHON) -m alembic revision --autogenerate -m "$(message)"; \
|
||||
fi
|
||||
endif
|
||||
|
||||
migrate-create-manual:
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@if "$(message)"=="" (echo Error: Please provide a message. Usage: make migrate-create-manual message="your_description") else ($(PYTHON) -m alembic revision -m "$(message)")
|
||||
else
|
||||
@if [ -z "$(message)" ]; then \
|
||||
echo "Error: Please provide a message. Usage: make migrate-create-manual message=\"your_description\""; \
|
||||
else \
|
||||
$(PYTHON) -m alembic revision -m "$(message)"; \
|
||||
fi
|
||||
endif
|
||||
|
||||
migrate-up:
|
||||
@echo Running database migrations...
|
||||
@echo "Running database migrations..."
|
||||
$(PYTHON) -m alembic upgrade head
|
||||
@echo Migrations completed successfully
|
||||
@echo "✅ Migrations completed successfully"
|
||||
|
||||
migrate-down:
|
||||
@echo Rolling back last migration...
|
||||
@echo "Rolling back last migration..."
|
||||
$(PYTHON) -m alembic downgrade -1
|
||||
@echo Rollback completed
|
||||
@echo "✅ Rollback completed"
|
||||
|
||||
migrate-status:
|
||||
@echo Current migration status:
|
||||
@echo "Current migration status:"
|
||||
$(PYTHON) -m alembic current
|
||||
@echo.
|
||||
@echo Migration history:
|
||||
@echo ""
|
||||
@echo "Migration history:"
|
||||
$(PYTHON) -m alembic history --verbose
|
||||
|
||||
# =============================================================================
|
||||
# DATABASE INITIALIZATION & SEEDING
|
||||
# =============================================================================
|
||||
|
||||
init-prod:
|
||||
@echo "🔧 Initializing production database..."
|
||||
$(PYTHON) scripts/init_production.py
|
||||
@echo "✅ Production initialization completed"
|
||||
|
||||
# Demo data seeding - Cross-platform using Python to set environment
|
||||
seed-demo:
|
||||
@echo "🎪 Seeding demo data (normal mode)..."
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@set SEED_MODE=normal&& $(PYTHON) scripts/seed_demo.py
|
||||
else
|
||||
SEED_MODE=normal $(PYTHON) scripts/seed_demo.py
|
||||
endif
|
||||
@echo "✅ Demo seeding completed"
|
||||
|
||||
seed-demo-minimal:
|
||||
@echo "🎪 Seeding demo data (minimal mode - 1 vendor only)..."
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@set SEED_MODE=minimal&& $(PYTHON) scripts/seed_demo.py
|
||||
else
|
||||
SEED_MODE=minimal $(PYTHON) scripts/seed_demo.py
|
||||
endif
|
||||
@echo "✅ Minimal demo seeding completed"
|
||||
|
||||
seed-demo-reset:
|
||||
@echo "⚠️ WARNING: This will DELETE ALL existing data!"
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@set SEED_MODE=reset&& $(PYTHON) scripts/seed_demo.py
|
||||
else
|
||||
SEED_MODE=reset $(PYTHON) scripts/seed_demo.py
|
||||
endif
|
||||
|
||||
db-setup: migrate-up init-prod seed-demo
|
||||
@echo "✅ Database setup complete!"
|
||||
@echo "Run 'make dev' to start development server"
|
||||
|
||||
db-reset: migrate-down migrate-up seed-demo-reset
|
||||
@echo "✅ Database completely reset!"
|
||||
|
||||
backup-db:
|
||||
@echo Creating database backup...
|
||||
@echo "Creating database backup..."
|
||||
@$(PYTHON) scripts/backup_database.py
|
||||
|
||||
# Add these commands to the DATABASE section after backup-db:
|
||||
|
||||
seed:
|
||||
@echo Seeding database with comprehensive test data...
|
||||
$(PYTHON) scripts/seed_database.py
|
||||
@echo Seeding completed successfully
|
||||
|
||||
seed-minimal:
|
||||
@echo Seeding database with minimal data (admin + 1 vendor)...
|
||||
$(PYTHON) scripts/seed_database.py --minimal
|
||||
@echo Minimal seeding completed
|
||||
|
||||
seed-reset:
|
||||
@echo WARNING: This will DELETE ALL existing data!
|
||||
$(PYTHON) scripts/seed_database.py --reset
|
||||
@echo Database reset and seeded
|
||||
|
||||
# Complete database setup (migrate + seed)
|
||||
db-setup: migrate-up seed
|
||||
@echo Database setup complete!
|
||||
@echo Run 'make dev' to start development server
|
||||
|
||||
db-reset: migrate-down migrate-up seed-reset
|
||||
@echo Database completely reset!
|
||||
|
||||
# =============================================================================
|
||||
# TESTING
|
||||
# =============================================================================
|
||||
@@ -142,54 +169,54 @@ test-inventory:
|
||||
# =============================================================================
|
||||
|
||||
format:
|
||||
@echo Running black...
|
||||
@echo "Running black..."
|
||||
$(PYTHON) -m black . --exclude venv
|
||||
@echo Running isort...
|
||||
@echo "Running isort..."
|
||||
$(PYTHON) -m isort . --skip venv
|
||||
|
||||
lint:
|
||||
@echo Running linting...
|
||||
@echo "Running linting..."
|
||||
$(PYTHON) -m ruff check . --exclude venv
|
||||
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
|
||||
|
||||
# Alternative lint if still using flake8
|
||||
lint-flake8:
|
||||
@echo Running linting...
|
||||
@echo "Running linting..."
|
||||
$(PYTHON) -m flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,__pycache__,.git
|
||||
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
|
||||
|
||||
# Combined format and lint
|
||||
check: format lint
|
||||
|
||||
# Combined test with coverage and linting
|
||||
ci: format lint test-coverage
|
||||
|
||||
# Quality assurance workflow
|
||||
qa: format lint test-coverage docs-check
|
||||
@echo Quality assurance checks completed!
|
||||
@echo "Quality assurance checks completed!"
|
||||
|
||||
# =============================================================================
|
||||
# DOCUMENTATION
|
||||
# =============================================================================
|
||||
|
||||
docs-serve:
|
||||
@echo Starting documentation server...
|
||||
@echo "Starting documentation server..."
|
||||
$(PYTHON) -m mkdocs serve --dev-addr=0.0.0.0:8001
|
||||
|
||||
docs-build:
|
||||
@echo Building documentation...
|
||||
@echo "Building documentation..."
|
||||
$(PYTHON) -m mkdocs build --clean --strict
|
||||
|
||||
docs-deploy:
|
||||
@echo Deploying documentation...
|
||||
@echo "Deploying documentation..."
|
||||
$(PYTHON) -m mkdocs gh-deploy --clean
|
||||
|
||||
docs-clean:
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@if exist site rmdir /s /q site
|
||||
@echo Documentation build files cleaned!
|
||||
else
|
||||
@rm -rf site
|
||||
endif
|
||||
@echo "Documentation build files cleaned!"
|
||||
|
||||
docs-check:
|
||||
@echo Checking documentation for issues...
|
||||
@echo "Checking documentation for issues..."
|
||||
$(PYTHON) -m mkdocs build --strict --verbose
|
||||
|
||||
# =============================================================================
|
||||
@@ -222,30 +249,43 @@ deploy-prod: migrate-up
|
||||
# =============================================================================
|
||||
|
||||
clean:
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@if exist htmlcov rmdir /s /q htmlcov
|
||||
@if exist .pytest_cache rmdir /s /q .pytest_cache
|
||||
@if exist .coverage del .coverage
|
||||
@if exist .mypy_cache rmdir /s /q .mypy_cache
|
||||
@for /d /r . %%d in (__pycache__) do @if exist "%%d" rmdir /s /q "%%d"
|
||||
@del /s /q *.pyc 2>nul || echo No .pyc files found
|
||||
else
|
||||
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||
@find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||
@rm -rf htmlcov .pytest_cache .coverage .mypy_cache 2>/dev/null || true
|
||||
endif
|
||||
@echo "Cleaned up build artifacts"
|
||||
|
||||
verify-setup:
|
||||
@echo Running setup verification...
|
||||
@echo "Running setup verification..."
|
||||
@$(PYTHON) scripts/verify_setup.py
|
||||
|
||||
# Check Python and virtual environment
|
||||
check-env:
|
||||
@echo Checking Python environment...
|
||||
@echo Python version:
|
||||
@echo "Checking Python environment..."
|
||||
@echo "Detected OS: $(DETECTED_OS)"
|
||||
@echo ""
|
||||
@echo "Python version:"
|
||||
@$(PYTHON) --version
|
||||
@echo.
|
||||
@echo Python location:
|
||||
@echo ""
|
||||
ifeq ($(DETECTED_OS),Windows)
|
||||
@echo "Python location:"
|
||||
@where $(PYTHON)
|
||||
@echo.
|
||||
@echo Virtual environment active:
|
||||
else
|
||||
@echo "Python location:"
|
||||
@which $(PYTHON)
|
||||
endif
|
||||
@echo ""
|
||||
@echo "Virtual environment active:"
|
||||
@$(PYTHON) -c "import sys; print('YES' if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) else 'NO')"
|
||||
@echo.
|
||||
@echo Python executable:
|
||||
@echo ""
|
||||
@echo "Python executable:"
|
||||
@$(PYTHON) -c "import sys; print(sys.executable)"
|
||||
|
||||
# =============================================================================
|
||||
@@ -253,75 +293,92 @@ check-env:
|
||||
# =============================================================================
|
||||
|
||||
help:
|
||||
@echo LetzShop API Development Commands
|
||||
@echo.
|
||||
@echo === SETUP ===
|
||||
@echo install - Install production dependencies
|
||||
@echo install-test - Install test dependencies only
|
||||
@echo install-dev - Install development dependencies
|
||||
@echo install-all - Install all dependencies
|
||||
@echo setup - Complete development setup
|
||||
@echo.
|
||||
@echo === DEVELOPMENT ===
|
||||
@echo dev - Start API development server
|
||||
@echo dev-full - Start API and documentation servers
|
||||
@echo.
|
||||
@echo === DATABASE ===
|
||||
@echo migrate-create message="msg" - Create new migration
|
||||
@echo migrate-up - Apply pending migrations
|
||||
@echo migrate-down - Rollback last migration
|
||||
@echo migrate-status - Show migration status
|
||||
@echo backup-db - Backup database
|
||||
@echo.
|
||||
@echo === TESTING ===
|
||||
@echo test - Run all tests
|
||||
@echo test-coverage - Run tests with coverage
|
||||
@echo test-fast - Run fast tests only
|
||||
@echo.
|
||||
@echo === CODE QUALITY ===
|
||||
@echo format - Format code (black + isort)
|
||||
@echo lint - Run linting (ruff + mypy)
|
||||
@echo lint-flake8 - Run linting (flake8 + mypy - alternative)
|
||||
@echo check - Format + lint
|
||||
@echo ci - Full CI pipeline
|
||||
@echo qa - Quality assurance (format, lint, test, docs check)
|
||||
@echo.
|
||||
@echo === DOCUMENTATION ===
|
||||
@echo docs-serve - Start documentation server
|
||||
@echo docs-build - Build documentation
|
||||
@echo.
|
||||
@echo === DOCKER ===
|
||||
@echo docker-build - Build Docker containers
|
||||
@echo docker-up - Start Docker containers
|
||||
@echo docker-down - Stop Docker containers
|
||||
@echo docker-restart - Restart Docker containers
|
||||
@echo.
|
||||
@echo === DEPLOYMENT ===
|
||||
@echo deploy-staging - Deploy to staging environment
|
||||
@echo deploy-prod - Deploy to production environment
|
||||
@echo.
|
||||
@echo === UTILITIES ===
|
||||
@echo clean - Clean build artifacts
|
||||
@echo verify-setup - Verify project setup
|
||||
@echo check-env - Check Python and virtual environment
|
||||
@echo.
|
||||
@echo === DAILY WORKFLOW ===
|
||||
@echo make dev # Start development
|
||||
@echo make migrate-create message="feature" # Create migration
|
||||
@echo make migrate-up # Apply migration
|
||||
@echo make test # Run tests
|
||||
@echo "Wizamart Platform Development Commands"
|
||||
@echo ""
|
||||
@echo "=== SETUP ==="
|
||||
@echo " install - Install production dependencies"
|
||||
@echo " install-test - Install test dependencies only"
|
||||
@echo " install-dev - Install development dependencies"
|
||||
@echo " install-all - Install all dependencies"
|
||||
@echo " setup - Complete development setup"
|
||||
@echo ""
|
||||
@echo "=== DEVELOPMENT ==="
|
||||
@echo " dev - Start API development server"
|
||||
@echo ""
|
||||
@echo "=== DATABASE ==="
|
||||
@echo " migrate-create message=\"msg\" - Create new migration"
|
||||
@echo " migrate-up - Apply pending migrations"
|
||||
@echo " migrate-down - Rollback last migration"
|
||||
@echo " migrate-status - Show migration status"
|
||||
@echo " init-prod - Initialize production essentials"
|
||||
@echo " seed-demo - Seed demo data (3 vendors)"
|
||||
@echo " seed-demo-minimal - Seed minimal demo (1 vendor)"
|
||||
@echo " seed-demo-reset - DELETE ALL and reseed"
|
||||
@echo " db-setup - Full dev setup (migrate + init + seed)"
|
||||
@echo " backup-db - Backup database"
|
||||
@echo ""
|
||||
@echo "=== TESTING ==="
|
||||
@echo " test - Run all tests"
|
||||
@echo " test-coverage - Run tests with coverage"
|
||||
@echo " test-fast - Run fast tests only"
|
||||
@echo ""
|
||||
@echo "=== CODE QUALITY ==="
|
||||
@echo " format - Format code (black + isort)"
|
||||
@echo " lint - Run linting (ruff + mypy)"
|
||||
@echo " check - Format + lint"
|
||||
@echo " ci - Full CI pipeline"
|
||||
@echo " qa - Quality assurance"
|
||||
@echo ""
|
||||
@echo "=== DOCUMENTATION ==="
|
||||
@echo " docs-serve - Start documentation server"
|
||||
@echo " docs-build - Build documentation"
|
||||
@echo ""
|
||||
@echo "=== DOCKER ==="
|
||||
@echo " docker-build - Build Docker containers"
|
||||
@echo " docker-up - Start Docker containers"
|
||||
@echo " docker-down - Stop Docker containers"
|
||||
@echo ""
|
||||
@echo "=== UTILITIES ==="
|
||||
@echo " clean - Clean build artifacts"
|
||||
@echo " check-env - Check Python environment and OS"
|
||||
@echo ""
|
||||
@echo "=== DAILY WORKFLOW ==="
|
||||
@echo " make setup # Initial setup"
|
||||
@echo " make dev # Start development"
|
||||
@echo " make migrate-create message=\"feature\" # Create migration"
|
||||
@echo " make migrate-up # Apply migration"
|
||||
@echo " make test # Run tests"
|
||||
|
||||
help-db:
|
||||
@echo === DATABASE COMMANDS ===
|
||||
@echo migrate-create message="description" - Create auto-generated migration
|
||||
@echo migrate-create-manual message="desc" - Create empty migration template
|
||||
@echo migrate-up - Apply all pending migrations
|
||||
@echo migrate-down - Rollback last migration
|
||||
@echo migrate-status - Show current status and history
|
||||
@echo backup-db - Create database backup
|
||||
@echo.
|
||||
@echo TYPICAL WORKFLOW:
|
||||
@echo 1. Edit your SQLAlchemy models
|
||||
@echo 2. make migrate-create message="add_new_feature"
|
||||
@echo 3. Review the generated migration file
|
||||
@echo 4. make migrate-up
|
||||
@echo "=== DATABASE COMMANDS ==="
|
||||
@echo ""
|
||||
@echo "MIGRATIONS:"
|
||||
@echo " migrate-create message=\"description\" - Create auto-generated migration"
|
||||
@echo " migrate-create-manual message=\"desc\" - Create empty migration template"
|
||||
@echo " migrate-up - Apply all pending migrations"
|
||||
@echo " migrate-down - Rollback last migration"
|
||||
@echo " migrate-status - Show current status and history"
|
||||
@echo ""
|
||||
@echo "INITIALIZATION:"
|
||||
@echo " init-prod - Create admin user + settings (SAFE for production)"
|
||||
@echo ""
|
||||
@echo "DEMO DATA (Development only):"
|
||||
@echo " seed-demo - Create 3 demo vendors with data"
|
||||
@echo " seed-demo-minimal - Create 1 demo vendor only"
|
||||
@echo " seed-demo-reset - DELETE ALL data and reseed (DANGEROUS!)"
|
||||
@echo ""
|
||||
@echo "WORKFLOWS:"
|
||||
@echo " db-setup - Complete dev setup (migrate + init + seed)"
|
||||
@echo " db-reset - Nuclear option: rollback + reset + reseed"
|
||||
@echo ""
|
||||
@echo "TYPICAL FIRST-TIME SETUP:"
|
||||
@echo " 1. make migrate-up # Apply migrations"
|
||||
@echo " 2. make init-prod # Create admin user"
|
||||
@echo " 3. make seed-demo # Add demo data"
|
||||
@echo " 4. make dev # Start developing"
|
||||
@echo ""
|
||||
@echo "PRODUCTION SETUP:"
|
||||
@echo " 1. Set ENV=production or ENVIRONMENT=production"
|
||||
@echo " 2. make migrate-up"
|
||||
@echo " 3. make init-prod (with secure credentials in .env)"
|
||||
@echo " 4. Create vendors via admin panel"
|
||||
4
app/api/v1/vendor/__init__.py
vendored
4
app/api/v1/vendor/__init__.py
vendored
@@ -22,7 +22,7 @@ from . import (
|
||||
products,
|
||||
orders,
|
||||
customers,
|
||||
teams,
|
||||
team,
|
||||
inventory,
|
||||
marketplace,
|
||||
payments,
|
||||
@@ -55,7 +55,7 @@ router.include_router(settings.router, tags=["vendor-settings"])
|
||||
router.include_router(products.router, tags=["vendor-products"])
|
||||
router.include_router(orders.router, tags=["vendor-orders"])
|
||||
router.include_router(customers.router, tags=["vendor-customers"])
|
||||
router.include_router(teams.router, tags=["vendor-teams"])
|
||||
router.include_router(team.router, tags=["vendor-team"])
|
||||
router.include_router(inventory.router, tags=["vendor-inventory"])
|
||||
router.include_router(marketplace.router, tags=["vendor-marketplace"])
|
||||
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
# app/core/config.py
|
||||
"""Summary description ....
|
||||
"""
|
||||
Application configuration using Pydantic Settings.
|
||||
|
||||
This module provides classes and functions for:
|
||||
- ....
|
||||
- ....
|
||||
- ....
|
||||
- Configuration management via environment variables
|
||||
- Database settings
|
||||
- JWT and authentication configuration
|
||||
- Platform domain and multi-tenancy settings
|
||||
- Admin initialization settings
|
||||
|
||||
Note: Environment detection is handled by app.core.environment module.
|
||||
This module focuses purely on configuration storage and validation.
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic_settings import \
|
||||
BaseSettings # This is the correct import for Pydantic v2
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Settings class inheriting from BaseSettings that allows values to be overridden by environment variables."""
|
||||
"""
|
||||
Settings class for application configuration.
|
||||
|
||||
# Project information
|
||||
project_name: str = "Ecommerce Backend API with Marketplace Support"
|
||||
# Documentation
|
||||
documentation_url: str = "http://localhost:8001" # Development default
|
||||
Environment detection is delegated to app.core.environment.
|
||||
This class focuses on configuration values only.
|
||||
"""
|
||||
|
||||
# =============================================================================
|
||||
# PROJECT INFORMATION
|
||||
# =============================================================================
|
||||
project_name: str = "Wizamart - Multi-Vendor Marketplace Platform"
|
||||
version: str = "2.2.0"
|
||||
|
||||
# Clean description without HTML
|
||||
description: str = """
|
||||
@@ -34,49 +44,221 @@ class Settings(BaseSettings):
|
||||
**Documentation:** Visit /documentation for complete guides
|
||||
**API Testing:** Use /docs for interactive API exploration
|
||||
"""
|
||||
version: str = "2.2.0"
|
||||
|
||||
# Database
|
||||
database_url: str = "sqlite:///./ecommerce.db"
|
||||
# =============================================================================
|
||||
# DATABASE
|
||||
# =============================================================================
|
||||
database_url: str = "sqlite:///./wizamart.db"
|
||||
|
||||
# JWT
|
||||
# =============================================================================
|
||||
# ADMIN INITIALIZATION (for init_production.py)
|
||||
# =============================================================================
|
||||
admin_email: str = "admin@wizamart.com"
|
||||
admin_username: str = "admin"
|
||||
admin_password: str = "admin123" # CHANGE IN PRODUCTION!
|
||||
admin_first_name: str = "Platform"
|
||||
admin_last_name: str = "Administrator"
|
||||
|
||||
# =============================================================================
|
||||
# JWT AUTHENTICATION
|
||||
# =============================================================================
|
||||
jwt_secret_key: str = "change-this-in-production"
|
||||
jwt_expire_hours: int = 24
|
||||
jwt_expire_minutes: int = 30
|
||||
|
||||
# Middleware
|
||||
allowed_hosts: List[str] = ["*"] # Configure for production
|
||||
|
||||
# API
|
||||
# =============================================================================
|
||||
# API SERVER
|
||||
# =============================================================================
|
||||
api_host: str = "0.0.0.0"
|
||||
api_port: int = 8000
|
||||
debug: bool = False
|
||||
debug: bool = True
|
||||
|
||||
# =============================================================================
|
||||
# DOCUMENTATION
|
||||
# =============================================================================
|
||||
documentation_url: str = "http://localhost:8001"
|
||||
|
||||
# =============================================================================
|
||||
# MIDDLEWARE & SECURITY
|
||||
# =============================================================================
|
||||
allowed_hosts: List[str] = ["*"] # Configure for production
|
||||
|
||||
# Rate Limiting
|
||||
rate_limit_enabled: bool = True
|
||||
rate_limit_requests: int = 100
|
||||
rate_limit_window: int = 3600
|
||||
|
||||
# Logging
|
||||
# =============================================================================
|
||||
# LOGGING
|
||||
# =============================================================================
|
||||
log_level: str = "INFO"
|
||||
log_file: Optional[str] = None
|
||||
|
||||
# Platform domain configuration
|
||||
platform_domain: str = "platform.com" # Your main platform domain
|
||||
# =============================================================================
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
# =============================================================================
|
||||
platform_domain: str = "wizamart.com"
|
||||
|
||||
# Custom domain features
|
||||
allow_custom_domains: bool = True # Enable/disable custom domains
|
||||
require_domain_verification: bool = True # Require DNS verification
|
||||
allow_custom_domains: bool = True
|
||||
require_domain_verification: bool = True
|
||||
|
||||
# SSL/TLS configuration for custom domains
|
||||
ssl_provider: str = "letsencrypt" # or "cloudflare", "manual"
|
||||
auto_provision_ssl: bool = False # Set to True if using automated SSL
|
||||
auto_provision_ssl: bool = False
|
||||
|
||||
# DNS verification
|
||||
dns_verification_prefix: str = "_wizamart-verify"
|
||||
dns_verification_ttl: int = 3600
|
||||
|
||||
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
|
||||
# =============================================================================
|
||||
# PLATFORM LIMITS
|
||||
# =============================================================================
|
||||
max_vendors_per_user: int = 5
|
||||
max_team_members_per_vendor: int = 50
|
||||
invitation_expiry_days: int = 7
|
||||
|
||||
# =============================================================================
|
||||
# DEMO/SEED DATA CONFIGURATION
|
||||
# =============================================================================
|
||||
# Controls for demo data seeding
|
||||
seed_demo_vendors: int = 3 # Number of demo vendors to create
|
||||
seed_customers_per_vendor: int = 15 # Customers per vendor
|
||||
seed_products_per_vendor: int = 20 # Products per vendor
|
||||
seed_orders_per_vendor: int = 10 # Orders per vendor
|
||||
|
||||
model_config = {"env_file": ".env"}
|
||||
|
||||
|
||||
# Singleton settings instance
|
||||
settings = Settings()
|
||||
|
||||
# =============================================================================
|
||||
# ENVIRONMENT UTILITIES - Module-level functions
|
||||
# =============================================================================
|
||||
# Import environment detection utilities
|
||||
from app.core.environment import (
|
||||
get_environment,
|
||||
is_development,
|
||||
is_production,
|
||||
is_staging,
|
||||
should_use_secure_cookies
|
||||
)
|
||||
|
||||
|
||||
def get_current_environment() -> str:
|
||||
"""
|
||||
Get current environment.
|
||||
|
||||
Convenience function that delegates to app.core.environment.
|
||||
Use this when you need just the environment string.
|
||||
"""
|
||||
return get_environment()
|
||||
|
||||
|
||||
def is_production_environment() -> bool:
|
||||
"""
|
||||
Check if running in production.
|
||||
|
||||
Convenience function that delegates to app.core.environment.
|
||||
Use this for production-specific logic.
|
||||
"""
|
||||
return is_production()
|
||||
|
||||
|
||||
def is_development_environment() -> bool:
|
||||
"""
|
||||
Check if running in development.
|
||||
|
||||
Convenience function that delegates to app.core.environment.
|
||||
Use this for development-specific logic.
|
||||
"""
|
||||
return is_development()
|
||||
|
||||
|
||||
def is_staging_environment() -> bool:
|
||||
"""
|
||||
Check if running in staging.
|
||||
|
||||
Convenience function that delegates to app.core.environment.
|
||||
Use this for staging-specific logic.
|
||||
"""
|
||||
return is_staging()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# VALIDATION FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
def validate_production_settings() -> List[str]:
|
||||
"""
|
||||
Validate settings for production environment.
|
||||
|
||||
Returns:
|
||||
List of warning messages if configuration is insecure
|
||||
"""
|
||||
warnings = []
|
||||
|
||||
if is_production():
|
||||
# Check for default/insecure values
|
||||
if settings.admin_password == "admin123":
|
||||
warnings.append("⚠️ Using default admin password in production!")
|
||||
|
||||
if settings.jwt_secret_key == "change-this-in-production":
|
||||
warnings.append("⚠️ Using default JWT secret key in production!")
|
||||
|
||||
if settings.debug:
|
||||
warnings.append("⚠️ Debug mode enabled in production!")
|
||||
|
||||
if "*" in settings.allowed_hosts:
|
||||
warnings.append("⚠️ ALLOWED_HOSTS is set to wildcard (*) in production!")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
def print_environment_info():
|
||||
"""Print current environment configuration."""
|
||||
print("\n" + "=" * 70)
|
||||
print(f" ENVIRONMENT: {get_environment().upper()}")
|
||||
print("=" * 70)
|
||||
print(f" Database: {settings.database_url}")
|
||||
print(f" Debug mode: {settings.debug}")
|
||||
print(f" API port: {settings.api_port}")
|
||||
print(f" Platform: {settings.platform_domain}")
|
||||
print(f" Secure cookies: {should_use_secure_cookies()}")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
# Show warnings if in production
|
||||
if is_production():
|
||||
warnings = validate_production_settings()
|
||||
if warnings:
|
||||
print("\n⚠️ PRODUCTION WARNINGS:")
|
||||
for warning in warnings:
|
||||
print(f" {warning}")
|
||||
print()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PUBLIC API
|
||||
# =============================================================================
|
||||
__all__ = [
|
||||
# Settings singleton
|
||||
'settings',
|
||||
|
||||
# Environment detection (re-exported from app.core.environment)
|
||||
'get_environment',
|
||||
'is_development',
|
||||
'is_production',
|
||||
'is_staging',
|
||||
'should_use_secure_cookies',
|
||||
|
||||
# Convenience functions
|
||||
'get_current_environment',
|
||||
'is_production_environment',
|
||||
'is_development_environment',
|
||||
'is_staging_environment',
|
||||
|
||||
# Validation
|
||||
'validate_production_settings',
|
||||
'print_environment_info',
|
||||
]
|
||||
|
||||
1005
docs/__REVAMPING/SEED_DATA/DATABASE_INIT_GUIDE.md
Normal file
1005
docs/__REVAMPING/SEED_DATA/DATABASE_INIT_GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
247
docs/__REVAMPING/SEED_DATA/DATABASE_QUICK_REFERENCE_GUIDE.md
Normal file
247
docs/__REVAMPING/SEED_DATA/DATABASE_QUICK_REFERENCE_GUIDE.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Database Commands - Quick Reference
|
||||
|
||||
## 🚀 Common Workflows
|
||||
|
||||
### First-Time Development Setup
|
||||
```bash
|
||||
make migrate-up # Apply migrations
|
||||
make init-prod # Create admin user
|
||||
make seed-demo # Add demo data
|
||||
make dev # Start developing
|
||||
```
|
||||
|
||||
### First-Time Production Setup
|
||||
```bash
|
||||
# 1. Configure .env
|
||||
ENVIRONMENT=production
|
||||
ADMIN_EMAIL=admin@yourcompany.com
|
||||
ADMIN_PASSWORD=SecurePassword123!
|
||||
|
||||
# 2. Initialize
|
||||
make migrate-up
|
||||
make init-prod
|
||||
|
||||
# 3. Create vendors via admin panel
|
||||
```
|
||||
|
||||
### Daily Development
|
||||
```bash
|
||||
make dev # Start server
|
||||
make seed-demo-reset # Fresh demo data
|
||||
make test # Run tests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Command Reference
|
||||
|
||||
### Database Migrations
|
||||
```bash
|
||||
make migrate-create message="add_feature" # Create migration
|
||||
make migrate-up # Apply migrations
|
||||
make migrate-down # Rollback last
|
||||
make migrate-status # Check status
|
||||
```
|
||||
|
||||
### Initialization
|
||||
```bash
|
||||
make init-prod # Create admin + settings (SAFE for production)
|
||||
```
|
||||
|
||||
### Demo Data (Development Only)
|
||||
```bash
|
||||
make seed-demo # 3 vendors + data
|
||||
make seed-demo-minimal # 1 vendor only
|
||||
make seed-demo-reset # DELETE ALL + reseed (DANGEROUS!)
|
||||
```
|
||||
|
||||
### Complete Workflows
|
||||
```bash
|
||||
make db-setup # migrate + init + seed
|
||||
make db-reset # rollback + migrate + reset
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration (.env)
|
||||
|
||||
### Required Settings
|
||||
```bash
|
||||
ENVIRONMENT=development # development/staging/production
|
||||
DATABASE_URL=sqlite:///./wizamart.db
|
||||
|
||||
# Admin credentials (CHANGE IN PRODUCTION!)
|
||||
ADMIN_EMAIL=admin@wizamart.com
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
```
|
||||
|
||||
### Demo Data Controls
|
||||
```bash
|
||||
SEED_DEMO_VENDORS=3 # How many vendors
|
||||
SEED_CUSTOMERS_PER_VENDOR=15 # Customers per vendor
|
||||
SEED_PRODUCTS_PER_VENDOR=20 # Products per vendor
|
||||
```
|
||||
|
||||
### Using Settings in Code
|
||||
```python
|
||||
from app.core.config import settings
|
||||
|
||||
# Environment checks
|
||||
if settings.is_production:
|
||||
# Production code
|
||||
|
||||
# Access configuration
|
||||
email = settings.admin_email
|
||||
db_url = settings.database_url
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Default Credentials
|
||||
|
||||
### Admin (After init-prod)
|
||||
```
|
||||
URL: http://localhost:8000/admin/login
|
||||
Username: admin
|
||||
Password: admin123 (CHANGE IN PRODUCTION!)
|
||||
```
|
||||
|
||||
### Demo Vendors (After seed-demo)
|
||||
```
|
||||
Vendor 1: vendor1@example.com / password123
|
||||
Vendor 2: vendor2@example.com / password123
|
||||
Vendor 3: vendor3@example.com / password123
|
||||
```
|
||||
|
||||
⚠️ **All demo passwords are INSECURE - for development only!**
|
||||
|
||||
---
|
||||
|
||||
## 📊 What Each Command Creates
|
||||
|
||||
### `make init-prod`
|
||||
✅ Platform admin user
|
||||
✅ Admin settings
|
||||
✅ Role templates
|
||||
✅ RBAC schema verification
|
||||
|
||||
**Safe for production**: YES
|
||||
**Contains fake data**: NO
|
||||
|
||||
### `make seed-demo`
|
||||
✅ 3 demo vendors
|
||||
✅ Demo vendor users
|
||||
✅ ~45 customers (15 per vendor)
|
||||
✅ ~60 products (20 per vendor)
|
||||
✅ Vendor themes
|
||||
✅ Custom domains
|
||||
|
||||
**Safe for production**: NO
|
||||
**Contains fake data**: YES - ALL OF IT
|
||||
|
||||
### `make seed-demo-minimal`
|
||||
✅ 1 demo vendor
|
||||
✅ 1 demo vendor user
|
||||
✅ ~15 customers
|
||||
✅ ~20 products
|
||||
✅ Vendor theme
|
||||
✅ Custom domain
|
||||
|
||||
**Safe for production**: NO
|
||||
**Contains fake data**: YES
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Safety Features
|
||||
|
||||
### Production Warnings
|
||||
```bash
|
||||
# Automatically warns if:
|
||||
⚠️ Using default admin password
|
||||
⚠️ Using default JWT secret
|
||||
⚠️ Debug mode enabled in production
|
||||
⚠️ ALLOWED_HOSTS is wildcard
|
||||
```
|
||||
|
||||
### Environment Protection
|
||||
```bash
|
||||
# seed_demo.py refuses to run if:
|
||||
ENVIRONMENT=production
|
||||
|
||||
Error: Cannot run demo seeding in production!
|
||||
```
|
||||
|
||||
### Reset Confirmation
|
||||
```bash
|
||||
make seed-demo-reset
|
||||
# Requires typing: DELETE ALL DATA
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Verification Commands
|
||||
|
||||
### Check Database State
|
||||
```python
|
||||
# Quick check
|
||||
python -c "
|
||||
from app.core.database import SessionLocal
|
||||
from models.database.vendor import Vendor
|
||||
db = SessionLocal()
|
||||
print(f'Vendors: {db.query(Vendor).count()}')
|
||||
db.close()
|
||||
"
|
||||
```
|
||||
|
||||
### View Settings
|
||||
```python
|
||||
# Check configuration
|
||||
python -c "
|
||||
from app.core.config import settings, print_environment_info
|
||||
print_environment_info()
|
||||
"
|
||||
```
|
||||
|
||||
### Validate Production
|
||||
```python
|
||||
# Check production security
|
||||
python -c "
|
||||
from app.core.config import validate_production_settings
|
||||
warnings = validate_production_settings()
|
||||
if warnings:
|
||||
for w in warnings: print(w)
|
||||
else:
|
||||
print('✅ Production configuration is secure')
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Common Issues
|
||||
|
||||
### "Admin already exists"
|
||||
✅ **Normal!** init_production.py is idempotent (safe to run multiple times)
|
||||
|
||||
### "Table doesn't exist"
|
||||
❌ Run migrations first: `make migrate-up`
|
||||
|
||||
### "Cannot run in production"
|
||||
✅ **Correct behavior!** seed_demo.py blocks production usage
|
||||
|
||||
### "Default password warning"
|
||||
⚠️ Update `.env` with secure password in production
|
||||
|
||||
---
|
||||
|
||||
## 📚 More Help
|
||||
|
||||
```bash
|
||||
make help # All commands
|
||||
make help-db # Database-specific help
|
||||
```
|
||||
|
||||
**Documentation**:
|
||||
- `DATABASE_INIT_GUIDE.md` - Detailed guide
|
||||
- `MIGRATION_GUIDE.md` - Migration from old system
|
||||
- `README.md` - Project overview
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
# Product Independence - Quick Reference
|
||||
|
||||
## TL;DR
|
||||
|
||||
**Problem:** Products must have a MarketplaceProduct entry, even for vendor-created products.
|
||||
|
||||
**Solution:** Make `marketplace_product_id` nullable and add core product fields to Product table.
|
||||
|
||||
**Impact:** 6-8 weeks implementation, requires database migration.
|
||||
|
||||
---
|
||||
|
||||
## Current Blocker for Seed Script
|
||||
|
||||
The seed script fails because it tries to create standalone products, but the current schema requires:
|
||||
|
||||
```python
|
||||
marketplace_product_id = Column(..., nullable=False) # ❌ MANDATORY
|
||||
```
|
||||
|
||||
### Temporary Workaround (Current Seed Script)
|
||||
|
||||
Create MarketplaceProduct for every Product until migration is complete:
|
||||
|
||||
```python
|
||||
# For each product:
|
||||
# 1. Create MarketplaceProduct
|
||||
# 2. Create Product linked to it
|
||||
```
|
||||
|
||||
This works but violates the desired architecture.
|
||||
|
||||
---
|
||||
|
||||
## Quick Decision Matrix
|
||||
|
||||
| Question | Answer | Priority |
|
||||
|----------|--------|----------|
|
||||
| Implement now? | Not urgent - current workaround functional | Medium |
|
||||
| Block for launch? | No - can ship with current architecture | N/A |
|
||||
| Technical debt? | Yes - should address in 1-2 quarters | Medium-High |
|
||||
|
||||
---
|
||||
|
||||
## Minimal Implementation (if needed quickly)
|
||||
|
||||
**Phase 1 Only - Make nullable:**
|
||||
|
||||
```python
|
||||
# 1 hour migration
|
||||
def upgrade():
|
||||
op.alter_column('products', 'marketplace_product_id', nullable=True)
|
||||
op.add_column('products', sa.Column('title', sa.String(), nullable=True))
|
||||
op.add_column('products', sa.Column('gtin', sa.String(), nullable=True))
|
||||
# Add only critical fields
|
||||
|
||||
# Updated model
|
||||
class Product:
|
||||
marketplace_product_id = Column(Integer, ForeignKey(...), nullable=True) # ✅
|
||||
title = Column(String, nullable=True) # Temp nullable
|
||||
gtin = Column(String, nullable=True)
|
||||
```
|
||||
|
||||
Then gradually add remaining fields in future migrations.
|
||||
|
||||
---
|
||||
|
||||
## Key Stakeholders to Consult
|
||||
|
||||
- [ ] Product Manager - Business impact, priority
|
||||
- [ ] Lead Developer - Technical approach, timeline
|
||||
- [ ] DevOps - Migration strategy, rollback plan
|
||||
- [ ] Vendors (if beta testing) - Feature importance
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review full migration plan:** `/outputs/PRODUCT_MIGRATION_PLAN.md`
|
||||
2. **Discuss with team** - Get buy-in on approach
|
||||
3. **Schedule implementation** - Based on priority
|
||||
4. **Create tracking ticket** - Link to migration plan
|
||||
|
||||
---
|
||||
|
||||
## For Now: Use Workaround
|
||||
|
||||
The updated `seed_demo.py` creates both MarketplaceProduct and Product.
|
||||
This is temporary until migration is implemented.
|
||||
|
||||
**No immediate action required** - continue development with current architecture.
|
||||
@@ -1,3 +0,0 @@
|
||||
@echo off
|
||||
call venv\Scripts\activate.bat
|
||||
C:\ProgramData\chocolatey\bin\make.exe %*
|
||||
@@ -11,7 +11,7 @@ This module defines request/response schemas for:
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel, EmailStr, Field, validator
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -72,7 +72,7 @@ class TeamMemberInvite(TeamMemberBase):
|
||||
description="Custom permissions (overrides role preset)"
|
||||
)
|
||||
|
||||
@validator('role_name')
|
||||
@field_validator('role_name')
|
||||
def validate_role_name(cls, v):
|
||||
"""Validate role name is in allowed presets."""
|
||||
if v is not None:
|
||||
@@ -83,7 +83,7 @@ class TeamMemberInvite(TeamMemberBase):
|
||||
)
|
||||
return v.lower() if v else v
|
||||
|
||||
@validator('custom_permissions')
|
||||
@field_validator('custom_permissions')
|
||||
def validate_custom_permissions(cls, v, values):
|
||||
"""Ensure either role_id/role_name OR custom_permissions is provided."""
|
||||
if v is not None and len(v) > 0:
|
||||
@@ -151,7 +151,7 @@ class InvitationAccept(BaseModel):
|
||||
first_name: str = Field(..., min_length=1, max_length=100)
|
||||
last_name: str = Field(..., min_length=1, max_length=100)
|
||||
|
||||
@validator('password')
|
||||
@field_validator('password')
|
||||
def validate_password_strength(cls, v):
|
||||
"""Validate password meets minimum requirements."""
|
||||
if len(v) < 8:
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create default admin user for the platform.
|
||||
|
||||
Usage:
|
||||
python scripts/create_admin.py
|
||||
|
||||
This script:
|
||||
- Creates a default admin user if one doesn't exist
|
||||
- Can be run multiple times safely (idempotent)
|
||||
- Should be run AFTER database migrations
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import SessionLocal, engine
|
||||
from models.database.user import User
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
# Default admin credentials
|
||||
DEFAULT_ADMIN_EMAIL = "admin@platform.com"
|
||||
DEFAULT_ADMIN_USERNAME = "admin"
|
||||
DEFAULT_ADMIN_PASSWORD = "admin123" # Change this in production!
|
||||
|
||||
|
||||
def create_admin_user(db: Session) -> bool:
|
||||
"""
|
||||
Create default admin user if it doesn't exist.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
bool: True if user was created, False if already exists
|
||||
"""
|
||||
auth_manager = AuthManager()
|
||||
|
||||
# Check if admin user already exists
|
||||
existing_admin = db.execute(
|
||||
select(User).where(User.username == DEFAULT_ADMIN_USERNAME)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing_admin:
|
||||
print(f"ℹ️ Admin user '{DEFAULT_ADMIN_USERNAME}' already exists")
|
||||
print(f" Email: {existing_admin.email}")
|
||||
print(f" Role: {existing_admin.role}")
|
||||
print(f" Active: {existing_admin.is_active}")
|
||||
return False
|
||||
|
||||
# Create new admin user
|
||||
print(f"📝 Creating admin user...")
|
||||
|
||||
admin_user = User(
|
||||
email=DEFAULT_ADMIN_EMAIL,
|
||||
username=DEFAULT_ADMIN_USERNAME,
|
||||
hashed_password=auth_manager.hash_password(DEFAULT_ADMIN_PASSWORD),
|
||||
role="admin",
|
||||
is_active=True
|
||||
)
|
||||
|
||||
db.add(admin_user)
|
||||
db.commit()
|
||||
db.refresh(admin_user)
|
||||
|
||||
print("\n✅ Admin user created successfully!")
|
||||
print("\n" + "=" * 50)
|
||||
print(" Admin Credentials:")
|
||||
print("=" * 50)
|
||||
print(f" Email: {DEFAULT_ADMIN_EMAIL}")
|
||||
print(f" Username: {DEFAULT_ADMIN_USERNAME}")
|
||||
print(f" Password: {DEFAULT_ADMIN_PASSWORD}")
|
||||
print("=" * 50)
|
||||
print("\n⚠️ IMPORTANT: Change the password after first login!")
|
||||
print(f" Login at: http://localhost:8000/static/admin/login.html")
|
||||
print()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def verify_database_ready() -> bool:
|
||||
"""
|
||||
Verify that database tables exist.
|
||||
|
||||
Returns:
|
||||
bool: True if database is ready, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Try to query the users table
|
||||
with engine.connect() as conn:
|
||||
from sqlalchemy import text
|
||||
result = conn.execute(
|
||||
text("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
|
||||
)
|
||||
tables = result.fetchall()
|
||||
return len(tables) > 0
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking database: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to create admin user."""
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(" Admin User Creation Script")
|
||||
print("=" * 50 + "\n")
|
||||
|
||||
# Step 1: Verify database is ready
|
||||
print("1️⃣ Checking database...")
|
||||
|
||||
if not verify_database_ready():
|
||||
print("\n❌ ERROR: Database not ready!")
|
||||
print("\n The 'users' table doesn't exist yet.")
|
||||
print(" Please run database migrations first:")
|
||||
print()
|
||||
print(" alembic upgrade head")
|
||||
print()
|
||||
print(" Or if using make:")
|
||||
print(" make migrate-up")
|
||||
print()
|
||||
sys.exit(1)
|
||||
|
||||
print(" ✓ Database is ready")
|
||||
|
||||
# Step 2: Create admin user
|
||||
print("\n2️⃣ Creating admin user...")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
created = create_admin_user(db)
|
||||
|
||||
if created:
|
||||
print("\n🎉 Setup complete! You can now:")
|
||||
print(" 1. Start the server: uvicorn main:app --reload")
|
||||
print(" 2. Login at: http://localhost:8000/static/admin/login.html")
|
||||
print()
|
||||
else:
|
||||
print("\n✓ No changes needed - admin user already exists")
|
||||
print()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ ERROR: Failed to create admin user")
|
||||
print(f" {type(e).__name__}: {e}")
|
||||
print()
|
||||
print(" Common issues:")
|
||||
print(" - Database migrations not run")
|
||||
print(" - Database connection issues")
|
||||
print(" - Permissions problems")
|
||||
print()
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
416
scripts/init_production.py
Normal file
416
scripts/init_production.py
Normal file
@@ -0,0 +1,416 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Production Database Initialization for Wizamart Platform
|
||||
|
||||
This script initializes ESSENTIAL data required for production:
|
||||
- Platform admin user
|
||||
- Default vendor 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 pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.config import settings, print_environment_info, validate_production_settings
|
||||
from app.core.environment import is_production, get_environment
|
||||
from models.database.user import User
|
||||
from models.database.admin import AdminSetting
|
||||
from middleware.auth import AuthManager
|
||||
from app.core.permissions import PermissionGroups
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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",
|
||||
first_name=settings.admin_first_name,
|
||||
last_name=settings.admin_last_name,
|
||||
is_active=True,
|
||||
is_email_verified=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
db.add(admin)
|
||||
db.flush()
|
||||
|
||||
print_success(f"Created admin user: {admin.email}")
|
||||
return admin
|
||||
|
||||
|
||||
def create_default_role_templates(db: Session) -> dict:
|
||||
"""Create default role templates (not tied to any vendor).
|
||||
|
||||
These serve as templates that can be copied when creating vendor-specific roles.
|
||||
Note: Actual roles are vendor-specific and created when vendors 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 vendor onboarding")
|
||||
|
||||
return {
|
||||
"manager": list(PermissionGroups.MANAGER),
|
||||
"staff": list(PermissionGroups.STAFF),
|
||||
"support": list(PermissionGroups.SUPPORT),
|
||||
"viewer": list(PermissionGroups.VIEWER),
|
||||
"marketing": list(PermissionGroups.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_vendors_per_user",
|
||||
"value": str(settings.max_vendors_per_user),
|
||||
"value_type": "integer",
|
||||
"description": "Maximum vendors a user can own",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "max_team_members_per_vendor",
|
||||
"value": str(settings.max_team_members_per_vendor),
|
||||
"value_type": "integer",
|
||||
"description": "Maximum team members per vendor",
|
||||
"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_vendor_verification",
|
||||
"value": "true",
|
||||
"value_type": "boolean",
|
||||
"description": "Require admin verification for new vendors",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "allow_custom_domains",
|
||||
"value": str(settings.allow_custom_domains).lower(),
|
||||
"value_type": "boolean",
|
||||
"description": "Allow vendors to use custom domains",
|
||||
"is_public": False,
|
||||
},
|
||||
{
|
||||
"key": "maintenance_mode",
|
||||
"value": "false",
|
||||
"value_type": "boolean",
|
||||
"description": "Enable maintenance mode",
|
||||
"is_public": True,
|
||||
},
|
||||
]
|
||||
|
||||
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"],
|
||||
description=setting_data.get("description"),
|
||||
is_public=setting_data.get("is_public", False),
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.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 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 vendor_users has RBAC columns
|
||||
if "vendor_users" in tables:
|
||||
vu_cols = {col["name"] for col in inspector.get_columns("vendor_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 vendor_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: Set up default role templates
|
||||
print_step(3, "Setting up role templates...")
|
||||
role_templates = create_default_role_templates(db)
|
||||
|
||||
# Step 4: Create admin settings
|
||||
print_step(4, "Creating admin settings...")
|
||||
create_admin_settings(db)
|
||||
|
||||
# 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()
|
||||
|
||||
print(f"\n📊 Database Status:")
|
||||
print(f" Admin users: {user_count}")
|
||||
print(f" Admin settings: {setting_count}")
|
||||
|
||||
print("\n" + "─" * 70)
|
||||
print("🔐 ADMIN CREDENTIALS")
|
||||
print("─" * 70)
|
||||
print(f" 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 vendor")
|
||||
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()
|
||||
File diff suppressed because it is too large
Load Diff
658
scripts/seed_demo.py
Normal file
658
scripts/seed_demo.py
Normal file
@@ -0,0 +1,658 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Demo Database Seeder for Wizamart Platform
|
||||
|
||||
Creates DEMO/TEST data for development and testing:
|
||||
- Demo vendors with realistic data
|
||||
- Test customers and addresses
|
||||
- Sample products
|
||||
- Demo orders
|
||||
- Vendor themes and custom domains
|
||||
- Test import jobs
|
||||
|
||||
⚠️ WARNING: This script creates FAKE DATA for development only!
|
||||
⚠️ NEVER run this in production!
|
||||
|
||||
Prerequisites:
|
||||
- Database migrations must be applied (make migrate-up)
|
||||
- Production initialization must be run (make init-prod)
|
||||
|
||||
Usage:
|
||||
make seed-demo # Normal demo seeding
|
||||
make seed-demo-minimal # Minimal seeding (1 vendor only)
|
||||
make seed-demo-reset # Delete all data and reseed (DANGEROUS!)
|
||||
|
||||
This script is idempotent when run normally.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select, delete
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.config import settings
|
||||
from app.core.environment import is_production, get_environment
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor, VendorUser, Role
|
||||
from models.database.vendor_domain import VendorDomain
|
||||
from models.database.vendor_theme import VendorTheme
|
||||
from models.database.customer import Customer, CustomerAddress
|
||||
from models.database.product import Product
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
from models.database.order import Order, OrderItem
|
||||
from models.database.admin import PlatformAlert
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
# =============================================================================
|
||||
# MODE DETECTION (from environment variable set by Makefile)
|
||||
# =============================================================================
|
||||
import os
|
||||
|
||||
SEED_MODE = os.getenv('SEED_MODE', 'normal') # normal, minimal, reset
|
||||
|
||||
# =============================================================================
|
||||
# DEMO DATA CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
# Demo vendor configurations
|
||||
DEMO_VENDORS = [
|
||||
{
|
||||
"vendor_code": "WIZAMART",
|
||||
"name": "WizaMart",
|
||||
"subdomain": "wizamart",
|
||||
"description": "Premium electronics and gadgets marketplace",
|
||||
"theme_preset": "modern",
|
||||
"custom_domain": "wizamart.shop",
|
||||
},
|
||||
{
|
||||
"vendor_code": "FASHIONHUB",
|
||||
"name": "Fashion Hub",
|
||||
"subdomain": "fashionhub",
|
||||
"description": "Trendy clothing and accessories",
|
||||
"theme_preset": "vibrant",
|
||||
"custom_domain": "fashionhub.store",
|
||||
},
|
||||
{
|
||||
"vendor_code": "BOOKSTORE",
|
||||
"name": "The Book Store",
|
||||
"subdomain": "bookstore",
|
||||
"description": "Books, magazines, and educational materials",
|
||||
"theme_preset": "classic",
|
||||
"custom_domain": None,
|
||||
},
|
||||
]
|
||||
|
||||
# Demo users (vendor owners)
|
||||
DEMO_VENDOR_USERS = [
|
||||
{
|
||||
"username": "vendor1",
|
||||
"email": "vendor1@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "John",
|
||||
"last_name": "Vendor",
|
||||
},
|
||||
{
|
||||
"username": "vendor2",
|
||||
"email": "vendor2@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "Jane",
|
||||
"last_name": "Merchant",
|
||||
},
|
||||
{
|
||||
"username": "vendor3",
|
||||
"email": "vendor3@example.com",
|
||||
"password": "password123",
|
||||
"first_name": "Bob",
|
||||
"last_name": "Seller",
|
||||
},
|
||||
]
|
||||
|
||||
# Theme presets
|
||||
THEME_PRESETS = {
|
||||
"modern": {
|
||||
"primary": "#3b82f6",
|
||||
"secondary": "#06b6d4",
|
||||
"accent": "#f59e0b",
|
||||
"background": "#f9fafb",
|
||||
"text": "#111827",
|
||||
},
|
||||
"classic": {
|
||||
"primary": "#1e40af",
|
||||
"secondary": "#7c3aed",
|
||||
"accent": "#dc2626",
|
||||
"background": "#ffffff",
|
||||
"text": "#374151",
|
||||
},
|
||||
"vibrant": {
|
||||
"primary": "#ec4899",
|
||||
"secondary": "#f59e0b",
|
||||
"accent": "#8b5cf6",
|
||||
"background": "#fef3c7",
|
||||
"text": "#78350f",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 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}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SAFETY CHECKS
|
||||
# =============================================================================
|
||||
|
||||
def check_environment():
|
||||
"""Prevent running demo seed in production."""
|
||||
|
||||
if is_production():
|
||||
print_error("Cannot run demo seeding in production!")
|
||||
print(" This script creates FAKE DATA for development only.")
|
||||
print(f" Current environment: {get_environment()}")
|
||||
print("\n To seed in production:")
|
||||
print(" 1. Set ENVIRONMENT=development in .env (or ENV=development)")
|
||||
print(" 2. Run: make seed-demo")
|
||||
sys.exit(1)
|
||||
|
||||
print_success(f"Environment check passed: {get_environment()}")
|
||||
|
||||
|
||||
def check_admin_exists(db: Session) -> bool:
|
||||
"""Check if admin user exists."""
|
||||
|
||||
admin = db.execute(
|
||||
select(User).where(User.role == "admin")
|
||||
).scalar_one_or_none()
|
||||
|
||||
if not admin:
|
||||
print_error("No admin user found!")
|
||||
print(" Run production initialization first:")
|
||||
print(" make init-prod")
|
||||
return False
|
||||
|
||||
print_success(f"Admin user exists: {admin.email}")
|
||||
return True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DATA DELETION (for reset mode)
|
||||
# =============================================================================
|
||||
|
||||
def reset_all_data(db: Session):
|
||||
"""Delete ALL data from database (except admin user)."""
|
||||
|
||||
print_warning("RESETTING ALL DATA...")
|
||||
print(" This will delete all vendors, customers, orders, etc.")
|
||||
print(" Admin user will be preserved.")
|
||||
|
||||
# Get confirmation
|
||||
response = input("\n Type 'DELETE ALL DATA' to confirm: ")
|
||||
if response != "DELETE ALL DATA":
|
||||
print(" Reset cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
# Delete in correct order (respecting foreign keys)
|
||||
tables_to_clear = [
|
||||
OrderItem,
|
||||
Order,
|
||||
CustomerAddress,
|
||||
Customer,
|
||||
MarketplaceImportJob,
|
||||
MarketplaceProduct,
|
||||
Product,
|
||||
VendorDomain,
|
||||
VendorTheme,
|
||||
Role,
|
||||
VendorUser,
|
||||
Vendor,
|
||||
PlatformAlert,
|
||||
]
|
||||
|
||||
for table in tables_to_clear:
|
||||
db.execute(delete(table))
|
||||
|
||||
# Delete non-admin users
|
||||
db.execute(delete(User).where(User.role != "admin"))
|
||||
|
||||
db.commit()
|
||||
print_success("All data deleted (admin preserved)")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# SEEDING FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
def create_demo_vendors(db: Session, auth_manager: AuthManager) -> List[Vendor]:
|
||||
"""Create demo vendors with users."""
|
||||
|
||||
vendors = []
|
||||
|
||||
# Determine how many vendors to create based on mode
|
||||
vendor_count = 1 if SEED_MODE == 'minimal' else settings.seed_demo_vendors
|
||||
vendors_to_create = DEMO_VENDORS[:vendor_count]
|
||||
users_to_create = DEMO_VENDOR_USERS[:vendor_count]
|
||||
|
||||
for vendor_data, user_data in zip(vendors_to_create, users_to_create):
|
||||
# Check if vendor already exists
|
||||
existing = db.execute(
|
||||
select(Vendor).where(Vendor.vendor_code == vendor_data["vendor_code"])
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
print_warning(f"Vendor already exists: {vendor_data['name']}")
|
||||
vendors.append(existing)
|
||||
continue
|
||||
|
||||
# Create vendor user
|
||||
vendor_user = User(
|
||||
username=user_data["username"],
|
||||
email=user_data["email"],
|
||||
hashed_password=auth_manager.hash_password(user_data["password"]),
|
||||
role="vendor",
|
||||
first_name=user_data["first_name"],
|
||||
last_name=user_data["last_name"],
|
||||
is_active=True,
|
||||
is_email_verified=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(vendor_user)
|
||||
db.flush()
|
||||
|
||||
# Create vendor
|
||||
vendor = Vendor(
|
||||
vendor_code=vendor_data["vendor_code"],
|
||||
name=vendor_data["name"],
|
||||
subdomain=vendor_data["subdomain"],
|
||||
description=vendor_data["description"],
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(vendor)
|
||||
db.flush()
|
||||
|
||||
# Link user to vendor as owner
|
||||
vendor_user_link = VendorUser(
|
||||
vendor_id=vendor.id,
|
||||
user_id=vendor_user.id,
|
||||
user_type="owner",
|
||||
is_active=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(vendor_user_link)
|
||||
|
||||
# Create vendor theme
|
||||
theme_colors = THEME_PRESETS.get(vendor_data["theme_preset"], THEME_PRESETS["modern"])
|
||||
theme = VendorTheme(
|
||||
vendor_id=vendor.id,
|
||||
theme_name=vendor_data["theme_preset"],
|
||||
primary_color=theme_colors["primary"],
|
||||
secondary_color=theme_colors["secondary"],
|
||||
accent_color=theme_colors["accent"],
|
||||
background_color=theme_colors["background"],
|
||||
text_color=theme_colors["text"],
|
||||
is_active=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(theme)
|
||||
|
||||
# Create custom domain if specified
|
||||
if vendor_data.get("custom_domain"):
|
||||
domain = VendorDomain(
|
||||
vendor_id=vendor.id,
|
||||
domain_name=vendor_data["custom_domain"],
|
||||
is_verified=True, # Auto-verified for demo
|
||||
is_active=True,
|
||||
verification_token=None,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(domain)
|
||||
|
||||
vendors.append(vendor)
|
||||
print_success(f"Created vendor: {vendor.name} ({vendor.vendor_code})")
|
||||
|
||||
db.flush()
|
||||
return vendors
|
||||
|
||||
|
||||
def create_demo_customers(db: Session, vendor: Vendor, auth_manager: AuthManager, count: int) -> List[Customer]:
|
||||
"""Create demo customers for a vendor."""
|
||||
|
||||
customers = []
|
||||
# Use a simple demo password for all customers
|
||||
demo_password = "customer123"
|
||||
|
||||
for i in range(1, count + 1):
|
||||
email = f"customer{i}@{vendor.subdomain}.example.com"
|
||||
customer_number = f"CUST-{vendor.vendor_code}-{i:04d}"
|
||||
|
||||
# Check if customer already exists
|
||||
existing_customer = db.query(Customer).filter(
|
||||
Customer.vendor_id == vendor.id,
|
||||
Customer.email == email
|
||||
).first()
|
||||
|
||||
if existing_customer:
|
||||
customers.append(existing_customer)
|
||||
continue # Skip creation, customer already exists
|
||||
|
||||
customer = Customer(
|
||||
vendor_id=vendor.id,
|
||||
email=email,
|
||||
hashed_password=auth_manager.hash_password(demo_password),
|
||||
first_name=f"Customer{i}",
|
||||
last_name=f"Test",
|
||||
phone=f"+352123456{i:03d}",
|
||||
customer_number=customer_number,
|
||||
is_active=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(customer)
|
||||
customers.append(customer)
|
||||
|
||||
db.flush()
|
||||
|
||||
new_count = len([c for c in customers if c.id is None or db.is_modified(c)])
|
||||
if new_count > 0:
|
||||
print_success(f"Created {new_count} customers for {vendor.name}")
|
||||
else:
|
||||
print_warning(f"Customers already exist for {vendor.name}")
|
||||
|
||||
return customers
|
||||
|
||||
|
||||
def create_demo_products(db: Session, vendor: Vendor, count: int) -> List[Product]:
|
||||
"""Create demo products for a vendor."""
|
||||
|
||||
products = []
|
||||
|
||||
for i in range(1, count + 1):
|
||||
marketplace_product_id = f"{vendor.vendor_code}-MP-{i:04d}"
|
||||
product_id = f"{vendor.vendor_code}-PROD-{i:03d}"
|
||||
|
||||
# Check if this product already exists
|
||||
existing_product = db.query(Product).filter(
|
||||
Product.vendor_id == vendor.id,
|
||||
Product.product_id == product_id
|
||||
).first()
|
||||
|
||||
if existing_product:
|
||||
products.append(existing_product)
|
||||
continue # Skip creation, product already exists
|
||||
|
||||
# Check if marketplace product already exists
|
||||
existing_mp = db.query(MarketplaceProduct).filter(
|
||||
MarketplaceProduct.marketplace_product_id == marketplace_product_id
|
||||
).first()
|
||||
|
||||
if existing_mp:
|
||||
marketplace_product = existing_mp
|
||||
else:
|
||||
# Create the MarketplaceProduct (base product data)
|
||||
marketplace_product = MarketplaceProduct(
|
||||
marketplace_product_id=marketplace_product_id,
|
||||
title=f"Sample Product {i} - {vendor.name}",
|
||||
description=f"This is a demo product for testing purposes in {vendor.name}. High quality and affordable.",
|
||||
link=f"https://{vendor.subdomain}.example.com/products/sample-{i}",
|
||||
image_link=f"https://{vendor.subdomain}.example.com/images/product-{i}.jpg",
|
||||
price=str(Decimal(f"{(i * 10) % 500 + 9.99}")), # Store as string
|
||||
brand=vendor.name,
|
||||
gtin=f"TEST{vendor.id:02d}{i:010d}",
|
||||
availability="in stock",
|
||||
condition="new",
|
||||
google_product_category="Electronics > Computers > Laptops",
|
||||
marketplace="Wizamart",
|
||||
vendor_name=vendor.name,
|
||||
currency="EUR",
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(marketplace_product)
|
||||
db.flush() # Flush to get the marketplace_product.id
|
||||
|
||||
# Create the Product (vendor-specific entry)
|
||||
product = Product(
|
||||
vendor_id=vendor.id,
|
||||
marketplace_product_id=marketplace_product.id,
|
||||
product_id=product_id,
|
||||
price=float(Decimal(f"{(i * 10) % 500 + 9.99}")), # Store as float
|
||||
availability="in stock",
|
||||
condition="new",
|
||||
currency="EUR",
|
||||
is_active=True,
|
||||
is_featured=(i % 5 == 0), # Every 5th product is featured
|
||||
display_order=i,
|
||||
min_quantity=1,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
db.add(product)
|
||||
products.append(product)
|
||||
|
||||
db.flush()
|
||||
|
||||
new_count = len([p for p in products if p.id is None or db.is_modified(p)])
|
||||
if new_count > 0:
|
||||
print_success(f"Created {new_count} products for {vendor.name}")
|
||||
else:
|
||||
print_warning(f"Products already exist for {vendor.name}")
|
||||
|
||||
return products
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MAIN SEEDING
|
||||
# =============================================================================
|
||||
|
||||
def seed_demo_data(db: Session, auth_manager: AuthManager):
|
||||
"""Seed demo data for development."""
|
||||
|
||||
print_header("DEMO DATA SEEDING")
|
||||
print(f" Mode: {SEED_MODE.upper()}")
|
||||
|
||||
# Step 1: Check environment
|
||||
print_step(1, "Checking environment...")
|
||||
check_environment()
|
||||
|
||||
# Step 2: Check admin exists
|
||||
print_step(2, "Verifying admin user...")
|
||||
if not check_admin_exists(db):
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Reset data if in reset mode
|
||||
if SEED_MODE == 'reset':
|
||||
print_step(3, "Resetting data...")
|
||||
reset_all_data(db)
|
||||
|
||||
# Step 4: Create vendors
|
||||
print_step(4, "Creating demo vendors...")
|
||||
vendors = create_demo_vendors(db, auth_manager)
|
||||
|
||||
# Step 5: Create customers
|
||||
print_step(5, "Creating demo customers...")
|
||||
for vendor in vendors:
|
||||
create_demo_customers(
|
||||
db,
|
||||
vendor,
|
||||
auth_manager,
|
||||
count=settings.seed_customers_per_vendor
|
||||
)
|
||||
|
||||
# Step 6: Create products
|
||||
print_step(6, "Creating demo products...")
|
||||
for vendor in vendors:
|
||||
create_demo_products(
|
||||
db,
|
||||
vendor,
|
||||
count=settings.seed_products_per_vendor
|
||||
)
|
||||
|
||||
# Commit all changes
|
||||
db.commit()
|
||||
print_success("All demo data committed")
|
||||
|
||||
|
||||
def print_summary(db: Session):
|
||||
"""Print seeding summary."""
|
||||
|
||||
print_header("SEEDING SUMMARY")
|
||||
|
||||
# Count records
|
||||
vendor_count = db.query(Vendor).count()
|
||||
user_count = db.query(User).count()
|
||||
customer_count = db.query(Customer).count()
|
||||
product_count = db.query(Product).count()
|
||||
|
||||
print(f"\n📊 Database Status:")
|
||||
print(f" Vendors: {vendor_count}")
|
||||
print(f" Users: {user_count}")
|
||||
print(f" Customers: {customer_count}")
|
||||
print(f" Products: {product_count}")
|
||||
|
||||
# Show vendor details
|
||||
vendors = db.query(Vendor).all()
|
||||
print(f"\n🏪 Demo Vendors:")
|
||||
for vendor in vendors:
|
||||
print(f"\n {vendor.name} ({vendor.vendor_code})")
|
||||
print(f" Subdomain: {vendor.subdomain}.{settings.platform_domain}")
|
||||
|
||||
# Query custom domains separately
|
||||
custom_domain = db.query(VendorDomain).filter(
|
||||
VendorDomain.vendor_id == vendor.id,
|
||||
VendorDomain.is_active == True
|
||||
).first()
|
||||
|
||||
if custom_domain:
|
||||
# Try different possible field names (model field might vary)
|
||||
domain_value = (
|
||||
getattr(custom_domain, 'domain', None) or
|
||||
getattr(custom_domain, 'domain_name', None) or
|
||||
getattr(custom_domain, 'name', None)
|
||||
)
|
||||
if domain_value:
|
||||
print(f" Custom: {domain_value}")
|
||||
|
||||
print(f" Status: {'✓ Active' if vendor.is_active else '✗ Inactive'}")
|
||||
|
||||
print(f"\n🔐 Demo Vendor Credentials:")
|
||||
print("─" * 70)
|
||||
for i, vendor_data in enumerate(DEMO_VENDOR_USERS[:vendor_count], 1):
|
||||
vendor = vendors[i - 1] if i <= len(vendors) else None
|
||||
print(f" Vendor {i}:")
|
||||
print(f" Username: {vendor_data['username']}")
|
||||
print(f" Email: {vendor_data['email']}")
|
||||
print(f" Password: {vendor_data['password']}")
|
||||
if vendor:
|
||||
print(f" Login: http://localhost:8000/vendor/{vendor.vendor_code}/login")
|
||||
print(f" or http://{vendor.subdomain}.localhost:8000/vendor/login")
|
||||
print()
|
||||
|
||||
print(f"\n🛒 Demo Customer Credentials:")
|
||||
print("─" * 70)
|
||||
print(f" All customers:")
|
||||
print(f" Email: customer1@{{subdomain}}.example.com")
|
||||
print(f" Password: customer123")
|
||||
print(f" (Replace {{subdomain}} with vendor subdomain, e.g., wizamart)")
|
||||
print()
|
||||
|
||||
print(f"\n🏪 Shop Access (Development):")
|
||||
print("─" * 70)
|
||||
for vendor in vendors:
|
||||
print(f" {vendor.name}:")
|
||||
print(f" Path-based: http://localhost:8000/vendors/{vendor.vendor_code}/shop/")
|
||||
print(f" Subdomain: http://{vendor.subdomain}.localhost:8000/")
|
||||
print()
|
||||
|
||||
print("⚠️ ALL DEMO CREDENTIALS ARE INSECURE - For development only!")
|
||||
|
||||
print("\n🚀 NEXT STEPS:")
|
||||
print(" 1. Start development: make dev")
|
||||
print(" 2. Login as vendor:")
|
||||
print(" • Path-based: http://localhost:8000/vendor/WIZAMART/login")
|
||||
print(" • Subdomain: http://wizamart.localhost:8000/vendor/login")
|
||||
print(f" 3. Visit vendor shop: http://localhost:8000/vendors/WIZAMART/shop/")
|
||||
print(f" 4. Admin panel: http://localhost:8000/admin/login")
|
||||
print(f" Username: {settings.admin_username}")
|
||||
print(f" Password: {settings.admin_password}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MAIN ENTRY POINT
|
||||
# =============================================================================
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
|
||||
print("\n" + "╔" + "═" * 68 + "╗")
|
||||
print("║" + " " * 20 + "DEMO DATA SEEDING" + " " * 31 + "║")
|
||||
print("╚" + "═" * 68 + "╝")
|
||||
|
||||
db = SessionLocal()
|
||||
auth_manager = AuthManager()
|
||||
|
||||
try:
|
||||
seed_demo_data(db, auth_manager)
|
||||
print_summary(db)
|
||||
|
||||
print_header("✅ DEMO SEEDING COMPLETED")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
db.rollback()
|
||||
print("\n\n⚠️ Seeding interrupted")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print_header("❌ SEEDING FAILED")
|
||||
print(f"\nError: {e}\n")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,66 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
echo Generating frontend structure with statistics...
|
||||
echo.
|
||||
|
||||
set OUTPUT=frontend-structure.txt
|
||||
|
||||
echo Frontend Folder Structure > %OUTPUT%
|
||||
echo Generated: %date% %time% >> %OUTPUT%
|
||||
echo ============================================================================== >> %OUTPUT%
|
||||
echo. >> %OUTPUT%
|
||||
|
||||
echo. >> %OUTPUT%
|
||||
echo ╔══════════════════════════════════════════════════════════════════╗ >> %OUTPUT%
|
||||
echo ║ JINJA2 TEMPLATES ║ >> %OUTPUT%
|
||||
echo ║ Location: app/templates ║ >> %OUTPUT%
|
||||
echo ╚══════════════════════════════════════════════════════════════════╝ >> %OUTPUT%
|
||||
echo. >> %OUTPUT%
|
||||
|
||||
tree /F /A app\templates >> %OUTPUT%
|
||||
|
||||
echo. >> %OUTPUT%
|
||||
echo. >> %OUTPUT%
|
||||
echo ╔══════════════════════════════════════════════════════════════════╗ >> %OUTPUT%
|
||||
echo ║ STATIC ASSETS ║ >> %OUTPUT%
|
||||
echo ║ Location: static ║ >> %OUTPUT%
|
||||
echo ╚══════════════════════════════════════════════════════════════════╝ >> %OUTPUT%
|
||||
echo. >> %OUTPUT%
|
||||
|
||||
tree /F /A static >> %OUTPUT%
|
||||
|
||||
echo. >> %OUTPUT%
|
||||
echo. >> %OUTPUT%
|
||||
echo ╔══════════════════════════════════════════════════════════════════╗ >> %OUTPUT%
|
||||
echo ║ STATISTICS ║ >> %OUTPUT%
|
||||
echo ╚══════════════════════════════════════════════════════════════════╝ >> %OUTPUT%
|
||||
echo. >> %OUTPUT%
|
||||
|
||||
echo Templates: >> %OUTPUT%
|
||||
echo - Total HTML files: >> %OUTPUT%
|
||||
dir /S /B app\templates\*.html 2>nul | find /C ".html" >> %OUTPUT%
|
||||
echo - Total Jinja2 files: >> %OUTPUT%
|
||||
dir /S /B app\templates\*.j2 2>nul | find /C ".j2" >> %OUTPUT%
|
||||
|
||||
echo. >> %OUTPUT%
|
||||
echo Static Assets: >> %OUTPUT%
|
||||
echo - JavaScript files: >> %OUTPUT%
|
||||
dir /S /B static\*.js 2>nul | find /C ".js" >> %OUTPUT%
|
||||
echo - CSS files: >> %OUTPUT%
|
||||
dir /S /B static\*.css 2>nul | find /C ".css" >> %OUTPUT%
|
||||
echo - Image files: >> %OUTPUT%
|
||||
for %%e in (png jpg jpeg gif svg webp ico) do (
|
||||
dir /S /B static\*.%%e 2>nul | find /C ".%%e" >> %OUTPUT%
|
||||
)
|
||||
|
||||
echo. >> %OUTPUT%
|
||||
echo ============================================================================== >> %OUTPUT%
|
||||
echo End of structure >> %OUTPUT%
|
||||
|
||||
echo.
|
||||
echo ✅ Structure saved to %OUTPUT%
|
||||
echo.
|
||||
echo Opening file...
|
||||
notepad %OUTPUT%
|
||||
|
||||
endlocal
|
||||
528
scripts/show_structure.py
Normal file
528
scripts/show_structure.py
Normal file
@@ -0,0 +1,528 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cross-platform structure generator for Wizamart project.
|
||||
Works on Windows, Linux, and macOS.
|
||||
|
||||
Usage:
|
||||
python show_structure.py frontend
|
||||
python show_structure.py backend
|
||||
python show_structure.py tests
|
||||
python show_structure.py all
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
def count_files(directory: str, pattern: str) -> int:
|
||||
"""Count files matching pattern in directory."""
|
||||
if not os.path.exists(directory):
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
for root, dirs, files in os.walk(directory):
|
||||
# Skip __pycache__ and other cache directories
|
||||
dirs[:] = [d for d in dirs if d not in ['__pycache__', '.pytest_cache', '.git', 'node_modules']]
|
||||
|
||||
for file in files:
|
||||
if pattern == '*' or file.endswith(pattern):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def get_tree_structure(directory: str, exclude_patterns: List[str] = None) -> str:
|
||||
"""Generate tree structure for directory."""
|
||||
if not os.path.exists(directory):
|
||||
return f"Directory {directory} not found"
|
||||
|
||||
# Try to use system tree command first
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
# Windows tree command
|
||||
result = subprocess.run(
|
||||
['tree', '/F', '/A', directory],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='replace'
|
||||
)
|
||||
return result.stdout
|
||||
else:
|
||||
# Linux/Mac tree command with exclusions
|
||||
exclude_args = []
|
||||
if exclude_patterns:
|
||||
exclude_args = ['-I', '|'.join(exclude_patterns)]
|
||||
|
||||
result = subprocess.run(
|
||||
['tree', '-F', '-a'] + exclude_args + [directory],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout
|
||||
except (subprocess.SubprocessError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
# Fallback: generate tree structure manually
|
||||
return generate_manual_tree(directory, exclude_patterns)
|
||||
|
||||
|
||||
def generate_manual_tree(directory: str, exclude_patterns: List[str] = None, prefix: str = "") -> str:
|
||||
"""Generate tree structure manually when tree command is not available."""
|
||||
if exclude_patterns is None:
|
||||
exclude_patterns = ['__pycache__', '.pytest_cache', '.git', 'node_modules', '*.pyc', '*.pyo']
|
||||
|
||||
output = []
|
||||
path = Path(directory)
|
||||
|
||||
try:
|
||||
items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
|
||||
|
||||
for i, item in enumerate(items):
|
||||
# Skip excluded patterns
|
||||
skip = False
|
||||
for pattern in exclude_patterns:
|
||||
if pattern.startswith('*'):
|
||||
# File extension pattern
|
||||
if item.name.endswith(pattern[1:]):
|
||||
skip = True
|
||||
break
|
||||
else:
|
||||
# Directory or exact name pattern
|
||||
if item.name == pattern or pattern in str(item):
|
||||
skip = True
|
||||
break
|
||||
|
||||
if skip:
|
||||
continue
|
||||
|
||||
is_last = i == len(items) - 1
|
||||
current_prefix = "└── " if is_last else "├── "
|
||||
|
||||
if item.is_dir():
|
||||
output.append(f"{prefix}{current_prefix}{item.name}/")
|
||||
extension = " " if is_last else "│ "
|
||||
subtree = generate_manual_tree(str(item), exclude_patterns, prefix + extension)
|
||||
if subtree:
|
||||
output.append(subtree)
|
||||
else:
|
||||
output.append(f"{prefix}{current_prefix}{item.name}")
|
||||
except PermissionError:
|
||||
output.append(f"{prefix}[Permission Denied]")
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def generate_frontend_structure() -> str:
|
||||
"""Generate frontend structure report."""
|
||||
output = []
|
||||
output.append("Frontend Folder Structure")
|
||||
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
output.append("=" * 78)
|
||||
output.append("")
|
||||
|
||||
# Templates section
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ JINJA2 TEMPLATES ║")
|
||||
output.append("║ Location: app/templates ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
output.append(get_tree_structure("app/templates"))
|
||||
|
||||
# Static assets section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ STATIC ASSETS ║")
|
||||
output.append("║ Location: static ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
output.append(get_tree_structure("static"))
|
||||
|
||||
# Documentation section (if exists)
|
||||
if os.path.exists("docs"):
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ DOCUMENTATION ║")
|
||||
output.append("║ Location: docs ║")
|
||||
output.append("║ (also listed in tools structure) ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
output.append("Note: Documentation is also included in tools structure")
|
||||
output.append(" for infrastructure/DevOps context.")
|
||||
|
||||
# Statistics section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ STATISTICS ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
output.append("Templates:")
|
||||
output.append(f" - Total HTML files: {count_files('app/templates', '.html')}")
|
||||
output.append(f" - Total Jinja2 files: {count_files('app/templates', '.j2')}")
|
||||
|
||||
output.append("")
|
||||
output.append("Static Assets:")
|
||||
output.append(f" - JavaScript files: {count_files('static', '.js')}")
|
||||
output.append(f" - CSS files: {count_files('static', '.css')}")
|
||||
output.append(" - Image files:")
|
||||
for ext in ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico']:
|
||||
count = count_files('static', f'.{ext}')
|
||||
if count > 0:
|
||||
output.append(f" - .{ext}: {count}")
|
||||
|
||||
if os.path.exists("docs"):
|
||||
output.append("")
|
||||
output.append("Documentation:")
|
||||
output.append(f" - Markdown files: {count_files('docs', '.md')}")
|
||||
output.append(f" - reStructuredText files: {count_files('docs', '.rst')}")
|
||||
|
||||
output.append("")
|
||||
output.append("=" * 78)
|
||||
output.append("End of structure")
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def generate_backend_structure() -> str:
|
||||
"""Generate backend structure report."""
|
||||
output = []
|
||||
output.append("Backend Folder Structure")
|
||||
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
output.append("=" * 78)
|
||||
output.append("")
|
||||
|
||||
exclude = ['__pycache__', '*.pyc', '*.pyo', '.pytest_cache', '*.egg-info', 'templates']
|
||||
|
||||
# Backend directories to include
|
||||
backend_dirs = [
|
||||
('app', 'Application Code'),
|
||||
('middleware', 'Middleware Components'),
|
||||
('models', 'Database Models'),
|
||||
('storage', 'File Storage'),
|
||||
('tasks', 'Background Tasks'),
|
||||
('logs', 'Application Logs'),
|
||||
]
|
||||
|
||||
for directory, title in backend_dirs:
|
||||
if os.path.exists(directory):
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append(f"║ {title.upper().center(62)} ║")
|
||||
output.append(f"║ Location: {directory + '/'.ljust(51)} ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
output.append(get_tree_structure(directory, exclude))
|
||||
|
||||
# Statistics section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ STATISTICS ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
output.append("Python Files by Directory:")
|
||||
total_py_files = 0
|
||||
for directory, title in backend_dirs:
|
||||
if os.path.exists(directory):
|
||||
count = count_files(directory, '.py')
|
||||
total_py_files += count
|
||||
output.append(f" - {directory}/: {count} files")
|
||||
|
||||
output.append(f" - Total Python files: {total_py_files}")
|
||||
|
||||
output.append("")
|
||||
output.append("Application Components (if in app/):")
|
||||
components = ['routes', 'services', 'schemas', 'exceptions', 'utils']
|
||||
for component in components:
|
||||
component_path = f"app/{component}"
|
||||
if os.path.exists(component_path):
|
||||
count = count_files(component_path, '.py')
|
||||
output.append(f" - app/{component}: {count} files")
|
||||
|
||||
output.append("")
|
||||
output.append("=" * 78)
|
||||
output.append("End of structure")
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def generate_tools_structure() -> str:
|
||||
"""Generate tools/infrastructure structure report."""
|
||||
output = []
|
||||
output.append("Tools & Infrastructure Structure")
|
||||
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
output.append("=" * 78)
|
||||
output.append("")
|
||||
|
||||
exclude = ['__pycache__', '*.pyc', '*.pyo', '.pytest_cache', '*.egg-info']
|
||||
|
||||
# Tools directories to include
|
||||
tools_dirs = [
|
||||
('alembic', 'Database Migrations'),
|
||||
('scripts', 'Utility Scripts'),
|
||||
('docker', 'Docker Configuration'),
|
||||
('docs', 'Documentation'),
|
||||
]
|
||||
|
||||
for directory, title in tools_dirs:
|
||||
if os.path.exists(directory):
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append(f"║ {title.upper().center(62)} ║")
|
||||
output.append(f"║ Location: {directory + '/'.ljust(51)} ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
output.append(get_tree_structure(directory, exclude))
|
||||
|
||||
# Configuration files section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ CONFIGURATION FILES ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
output.append("Root configuration files:")
|
||||
config_files = [
|
||||
('Makefile', 'Build automation'),
|
||||
('requirements.txt', 'Python dependencies'),
|
||||
('pyproject.toml', 'Python project config'),
|
||||
('setup.py', 'Python setup script'),
|
||||
('setup.cfg', 'Setup configuration'),
|
||||
('alembic.ini', 'Alembic migrations config'),
|
||||
('mkdocs.yml', 'MkDocs documentation config'),
|
||||
('Dockerfile', 'Docker image definition'),
|
||||
('docker-compose.yml', 'Docker services'),
|
||||
('.dockerignore', 'Docker ignore patterns'),
|
||||
('.gitignore', 'Git ignore patterns'),
|
||||
('.env.example', 'Environment variables template'),
|
||||
]
|
||||
|
||||
for file, description in config_files:
|
||||
if os.path.exists(file):
|
||||
output.append(f" ✓ {file.ljust(25)} - {description}")
|
||||
|
||||
# Statistics section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ STATISTICS ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
output.append("Database Migrations:")
|
||||
if os.path.exists("alembic/versions"):
|
||||
migration_count = count_files("alembic/versions", ".py")
|
||||
output.append(f" - Total migrations: {migration_count}")
|
||||
if migration_count > 0:
|
||||
# Get first and last migration
|
||||
try:
|
||||
migrations = sorted([f for f in os.listdir("alembic/versions") if f.endswith('.py')])
|
||||
if migrations:
|
||||
output.append(f" - First: {migrations[0][:40]}...")
|
||||
if len(migrations) > 1:
|
||||
output.append(f" - Latest: {migrations[-1][:40]}...")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
output.append(" - No alembic/versions directory found")
|
||||
|
||||
output.append("")
|
||||
output.append("Scripts:")
|
||||
if os.path.exists("scripts"):
|
||||
script_types = {
|
||||
'.py': 'Python scripts',
|
||||
'.sh': 'Shell scripts',
|
||||
'.bat': 'Batch scripts',
|
||||
}
|
||||
for ext, desc in script_types.items():
|
||||
count = count_files("scripts", ext)
|
||||
if count > 0:
|
||||
output.append(f" - {desc}: {count}")
|
||||
else:
|
||||
output.append(" - No scripts directory found")
|
||||
|
||||
output.append("")
|
||||
output.append("Documentation:")
|
||||
if os.path.exists("docs"):
|
||||
doc_types = {
|
||||
'.md': 'Markdown files',
|
||||
'.rst': 'reStructuredText files',
|
||||
}
|
||||
for ext, desc in doc_types.items():
|
||||
count = count_files("docs", ext)
|
||||
if count > 0:
|
||||
output.append(f" - {desc}: {count}")
|
||||
else:
|
||||
output.append(" - No docs directory found")
|
||||
|
||||
output.append("")
|
||||
output.append("Docker:")
|
||||
docker_files = ['Dockerfile', 'docker-compose.yml', '.dockerignore']
|
||||
docker_exists = any(os.path.exists(f) for f in docker_files)
|
||||
if docker_exists:
|
||||
output.append(" ✓ Docker configuration present")
|
||||
if os.path.exists("docker"):
|
||||
output.append(f" - Docker directory files: {count_files('docker', '*')}")
|
||||
else:
|
||||
output.append(" - No Docker configuration found")
|
||||
|
||||
output.append("")
|
||||
output.append("=" * 78)
|
||||
output.append("End of structure")
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def generate_test_structure() -> str:
|
||||
"""Generate test structure report."""
|
||||
output = []
|
||||
output.append("Test Folder Structure")
|
||||
output.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
output.append("=" * 78)
|
||||
output.append("")
|
||||
|
||||
# Test files section
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ TEST FILES ║")
|
||||
output.append("║ Location: tests/ ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
exclude = ['__pycache__', '*.pyc', '*.pyo', '.pytest_cache', '*.egg-info']
|
||||
output.append(get_tree_structure("tests", exclude))
|
||||
|
||||
# Configuration section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ TEST CONFIGURATION ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
output.append("Test configuration files:")
|
||||
test_config_files = ['pytest.ini', 'conftest.py', 'tests/conftest.py', '.coveragerc']
|
||||
for file in test_config_files:
|
||||
if os.path.exists(file):
|
||||
output.append(f" ✓ {file}")
|
||||
|
||||
# Statistics section
|
||||
output.append("")
|
||||
output.append("")
|
||||
output.append("╔══════════════════════════════════════════════════════════════════╗")
|
||||
output.append("║ STATISTICS ║")
|
||||
output.append("╚══════════════════════════════════════════════════════════════════╝")
|
||||
output.append("")
|
||||
|
||||
# Count test files
|
||||
test_file_count = 0
|
||||
if os.path.exists("tests"):
|
||||
for root, dirs, files in os.walk("tests"):
|
||||
dirs[:] = [d for d in dirs if d != '__pycache__']
|
||||
for file in files:
|
||||
if file.startswith("test_") and file.endswith(".py"):
|
||||
test_file_count += 1
|
||||
|
||||
output.append("Test Files:")
|
||||
output.append(f" - Total test files: {test_file_count}")
|
||||
|
||||
output.append("")
|
||||
output.append("By Category:")
|
||||
categories = ['unit', 'integration', 'system', 'e2e', 'performance']
|
||||
for category in categories:
|
||||
category_path = f"tests/{category}"
|
||||
if os.path.exists(category_path):
|
||||
count = 0
|
||||
for root, dirs, files in os.walk(category_path):
|
||||
dirs[:] = [d for d in dirs if d != '__pycache__']
|
||||
for file in files:
|
||||
if file.startswith("test_") and file.endswith(".py"):
|
||||
count += 1
|
||||
output.append(f" - tests/{category}: {count} files")
|
||||
|
||||
# Count test functions
|
||||
test_function_count = 0
|
||||
if os.path.exists("tests"):
|
||||
for root, dirs, files in os.walk("tests"):
|
||||
dirs[:] = [d for d in dirs if d != '__pycache__']
|
||||
for file in files:
|
||||
if file.startswith("test_") and file.endswith(".py"):
|
||||
filepath = os.path.join(root, file)
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if line.strip().startswith("def test_"):
|
||||
test_function_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
output.append("")
|
||||
output.append("Test Functions:")
|
||||
output.append(f" - Total test functions: {test_function_count}")
|
||||
|
||||
output.append("")
|
||||
output.append("Coverage Files:")
|
||||
if os.path.exists(".coverage"):
|
||||
output.append(" ✓ .coverage (coverage data file exists)")
|
||||
if os.path.exists("htmlcov"):
|
||||
output.append(" ✓ htmlcov/ (HTML coverage report exists)")
|
||||
|
||||
output.append("")
|
||||
output.append("=" * 78)
|
||||
output.append("End of structure")
|
||||
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python show_structure.py [frontend|backend|tests|tools|all]")
|
||||
sys.exit(1)
|
||||
|
||||
structure_type = sys.argv[1].lower()
|
||||
|
||||
generators = {
|
||||
'frontend': ('frontend-structure.txt', generate_frontend_structure),
|
||||
'backend': ('backend-structure.txt', generate_backend_structure),
|
||||
'tests': ('test-structure.txt', generate_test_structure),
|
||||
'tools': ('tools-structure.txt', generate_tools_structure),
|
||||
}
|
||||
|
||||
if structure_type == 'all':
|
||||
for name, (filename, generator) in generators.items():
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"Generating {name} structure...")
|
||||
print('=' * 60)
|
||||
content = generator()
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print(f"✅ {name.capitalize()} structure saved to {filename}")
|
||||
print(f"\n{content}\n")
|
||||
elif structure_type in generators:
|
||||
filename, generator = generators[structure_type]
|
||||
print(f"Generating {structure_type} structure...")
|
||||
content = generator()
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print(f"\n✅ Structure saved to {filename}\n")
|
||||
print(content)
|
||||
else:
|
||||
print(f"Error: Unknown structure type '{structure_type}'")
|
||||
print("Valid options: frontend, backend, tests, tools, all")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user