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
|
# PROJECT INFORMATION
|
||||||
DESCRIPTION=Advanced product management system with JWT authentication
|
# =============================================================================
|
||||||
VERSION=0.0.1
|
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
|
# DATABASE CONFIGURATION
|
||||||
# For development, you can use SQLite:
|
# =============================================================================
|
||||||
|
# For development (SQLite)
|
||||||
DATABASE_URL=sqlite:///./wizamart.db
|
DATABASE_URL=sqlite:///./wizamart.db
|
||||||
|
|
||||||
# Documentation
|
# For production (PostgreSQL)
|
||||||
# .env.development
|
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
|
||||||
DOCUMENTATION_URL=http://localhost:8001
|
|
||||||
# .env.production
|
|
||||||
# DOCUMENTATION_URL=https://yourdomain.com/docs
|
|
||||||
# .env.staging
|
|
||||||
# DOCUMENTATION_URL=https://staging-docs.yourdomain.com
|
|
||||||
|
|
||||||
# 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_SECRET_KEY=your-super-secret-jwt-key-change-in-production
|
||||||
JWT_EXPIRE_HOURS=24
|
JWT_EXPIRE_HOURS=24
|
||||||
JWT_EXPIRE_MINUTES=30
|
JWT_EXPIRE_MINUTES=30
|
||||||
|
|
||||||
# API Configuration
|
# =============================================================================
|
||||||
|
# API SERVER
|
||||||
|
# =============================================================================
|
||||||
API_HOST=0.0.0.0
|
API_HOST=0.0.0.0
|
||||||
API_PORT=8000
|
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_ENABLED=True
|
||||||
RATE_LIMIT_REQUESTS=100
|
RATE_LIMIT_REQUESTS=100
|
||||||
RATE_LIMIT_WINDOW=3600
|
RATE_LIMIT_WINDOW=3600
|
||||||
|
|
||||||
# Logging
|
# =============================================================================
|
||||||
|
# LOGGING
|
||||||
|
# =============================================================================
|
||||||
LOG_LEVEL=DEBUG
|
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
|
# Custom domain features
|
||||||
ALLOW_CUSTOM_DOMAINS=True # Enable/disable custom domains
|
# Enable/disable custom domains
|
||||||
REQUIRE_DOMAIN_VERIFICATION=True # Require DNS verification
|
ALLOW_CUSTOM_DOMAINS=True
|
||||||
|
# Require DNS verification
|
||||||
|
REQUIRE_DOMAIN_VERIFICATION=True
|
||||||
|
|
||||||
# SSL/TLS configuration for custom domains
|
# SSL/TLS configuration
|
||||||
SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
|
# "letsencrypt" or "cloudflare", "manual"
|
||||||
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
SSL_PROVIDER=letsencrypt
|
||||||
|
# Set to True if using automated SSL
|
||||||
|
AUTO_PROVISION_SSL=False
|
||||||
|
|
||||||
# DNS verification
|
# DNS verification
|
||||||
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
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
|
.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
|
PYTHON := python
|
||||||
PIP := pip
|
PIP := pip
|
||||||
|
|
||||||
@@ -23,9 +32,9 @@ install-docs:
|
|||||||
|
|
||||||
install-all: install install-dev install-test install-docs
|
install-all: install install-dev install-test install-docs
|
||||||
|
|
||||||
setup: install-all migrate-up
|
setup: install-all migrate-up init-prod
|
||||||
@echo Development environment setup complete!
|
@echo "✅ Development environment setup complete!"
|
||||||
@echo Run 'make dev' to start development server
|
@echo "Run 'make dev' to start development server"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# DEVELOPMENT SERVERS
|
# DEVELOPMENT SERVERS
|
||||||
@@ -34,78 +43,96 @@ setup: install-all migrate-up
|
|||||||
dev:
|
dev:
|
||||||
$(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
$(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
|
# DATABASE MIGRATIONS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
migrate-create:
|
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\""; \
|
echo "Error: Please provide a message. Usage: make migrate-create message=\"your_description\""; \
|
||||||
else \
|
else \
|
||||||
$(PYTHON) -m alembic revision --autogenerate -m "$(message)"; \
|
$(PYTHON) -m alembic revision --autogenerate -m "$(message)"; \
|
||||||
fi
|
fi
|
||||||
|
endif
|
||||||
|
|
||||||
migrate-create-manual:
|
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)")
|
@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:
|
migrate-up:
|
||||||
@echo Running database migrations...
|
@echo "Running database migrations..."
|
||||||
$(PYTHON) -m alembic upgrade head
|
$(PYTHON) -m alembic upgrade head
|
||||||
@echo Migrations completed successfully
|
@echo "✅ Migrations completed successfully"
|
||||||
|
|
||||||
migrate-down:
|
migrate-down:
|
||||||
@echo Rolling back last migration...
|
@echo "Rolling back last migration..."
|
||||||
$(PYTHON) -m alembic downgrade -1
|
$(PYTHON) -m alembic downgrade -1
|
||||||
@echo Rollback completed
|
@echo "✅ Rollback completed"
|
||||||
|
|
||||||
migrate-status:
|
migrate-status:
|
||||||
@echo Current migration status:
|
@echo "Current migration status:"
|
||||||
$(PYTHON) -m alembic current
|
$(PYTHON) -m alembic current
|
||||||
@echo.
|
@echo ""
|
||||||
@echo Migration history:
|
@echo "Migration history:"
|
||||||
$(PYTHON) -m alembic history --verbose
|
$(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:
|
backup-db:
|
||||||
@echo Creating database backup...
|
@echo "Creating database backup..."
|
||||||
@$(PYTHON) scripts/backup_database.py
|
@$(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
|
# TESTING
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -142,54 +169,54 @@ test-inventory:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@echo Running black...
|
@echo "Running black..."
|
||||||
$(PYTHON) -m black . --exclude venv
|
$(PYTHON) -m black . --exclude venv
|
||||||
@echo Running isort...
|
@echo "Running isort..."
|
||||||
$(PYTHON) -m isort . --skip venv
|
$(PYTHON) -m isort . --skip venv
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@echo Running linting...
|
@echo "Running linting..."
|
||||||
$(PYTHON) -m ruff check . --exclude venv
|
$(PYTHON) -m ruff check . --exclude venv
|
||||||
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
|
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
|
||||||
|
|
||||||
# Alternative lint if still using flake8
|
|
||||||
lint-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 flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,__pycache__,.git
|
||||||
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
|
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
|
||||||
|
|
||||||
# Combined format and lint
|
|
||||||
check: format lint
|
check: format lint
|
||||||
|
|
||||||
# Combined test with coverage and linting
|
|
||||||
ci: format lint test-coverage
|
ci: format lint test-coverage
|
||||||
|
|
||||||
# Quality assurance workflow
|
|
||||||
qa: format lint test-coverage docs-check
|
qa: format lint test-coverage docs-check
|
||||||
@echo Quality assurance checks completed!
|
@echo "Quality assurance checks completed!"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# DOCUMENTATION
|
# DOCUMENTATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
docs-serve:
|
docs-serve:
|
||||||
@echo Starting documentation server...
|
@echo "Starting documentation server..."
|
||||||
$(PYTHON) -m mkdocs serve --dev-addr=0.0.0.0:8001
|
$(PYTHON) -m mkdocs serve --dev-addr=0.0.0.0:8001
|
||||||
|
|
||||||
docs-build:
|
docs-build:
|
||||||
@echo Building documentation...
|
@echo "Building documentation..."
|
||||||
$(PYTHON) -m mkdocs build --clean --strict
|
$(PYTHON) -m mkdocs build --clean --strict
|
||||||
|
|
||||||
docs-deploy:
|
docs-deploy:
|
||||||
@echo Deploying documentation...
|
@echo "Deploying documentation..."
|
||||||
$(PYTHON) -m mkdocs gh-deploy --clean
|
$(PYTHON) -m mkdocs gh-deploy --clean
|
||||||
|
|
||||||
docs-clean:
|
docs-clean:
|
||||||
|
ifeq ($(DETECTED_OS),Windows)
|
||||||
@if exist site rmdir /s /q site
|
@if exist site rmdir /s /q site
|
||||||
@echo Documentation build files cleaned!
|
else
|
||||||
|
@rm -rf site
|
||||||
|
endif
|
||||||
|
@echo "Documentation build files cleaned!"
|
||||||
|
|
||||||
docs-check:
|
docs-check:
|
||||||
@echo Checking documentation for issues...
|
@echo "Checking documentation for issues..."
|
||||||
$(PYTHON) -m mkdocs build --strict --verbose
|
$(PYTHON) -m mkdocs build --strict --verbose
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -222,30 +249,43 @@ deploy-prod: migrate-up
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
ifeq ($(DETECTED_OS),Windows)
|
||||||
@if exist htmlcov rmdir /s /q htmlcov
|
@if exist htmlcov rmdir /s /q htmlcov
|
||||||
@if exist .pytest_cache rmdir /s /q .pytest_cache
|
@if exist .pytest_cache rmdir /s /q .pytest_cache
|
||||||
@if exist .coverage del .coverage
|
@if exist .coverage del .coverage
|
||||||
@if exist .mypy_cache rmdir /s /q .mypy_cache
|
@if exist .mypy_cache rmdir /s /q .mypy_cache
|
||||||
@for /d /r . %%d in (__pycache__) do @if exist "%%d" rmdir /s /q "%%d"
|
@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
|
@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:
|
verify-setup:
|
||||||
@echo Running setup verification...
|
@echo "Running setup verification..."
|
||||||
@$(PYTHON) scripts/verify_setup.py
|
@$(PYTHON) scripts/verify_setup.py
|
||||||
|
|
||||||
# Check Python and virtual environment
|
|
||||||
check-env:
|
check-env:
|
||||||
@echo Checking Python environment...
|
@echo "Checking Python environment..."
|
||||||
@echo Python version:
|
@echo "Detected OS: $(DETECTED_OS)"
|
||||||
|
@echo ""
|
||||||
|
@echo "Python version:"
|
||||||
@$(PYTHON) --version
|
@$(PYTHON) --version
|
||||||
@echo.
|
@echo ""
|
||||||
@echo Python location:
|
ifeq ($(DETECTED_OS),Windows)
|
||||||
|
@echo "Python location:"
|
||||||
@where $(PYTHON)
|
@where $(PYTHON)
|
||||||
@echo.
|
else
|
||||||
@echo Virtual environment active:
|
@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')"
|
@$(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 ""
|
||||||
@echo Python executable:
|
@echo "Python executable:"
|
||||||
@$(PYTHON) -c "import sys; print(sys.executable)"
|
@$(PYTHON) -c "import sys; print(sys.executable)"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -253,75 +293,92 @@ check-env:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo LetzShop API Development Commands
|
@echo "Wizamart Platform Development Commands"
|
||||||
@echo.
|
@echo ""
|
||||||
@echo === SETUP ===
|
@echo "=== SETUP ==="
|
||||||
@echo install - Install production dependencies
|
@echo " install - Install production dependencies"
|
||||||
@echo install-test - Install test dependencies only
|
@echo " install-test - Install test dependencies only"
|
||||||
@echo install-dev - Install development dependencies
|
@echo " install-dev - Install development dependencies"
|
||||||
@echo install-all - Install all dependencies
|
@echo " install-all - Install all dependencies"
|
||||||
@echo setup - Complete development setup
|
@echo " setup - Complete development setup"
|
||||||
@echo.
|
@echo ""
|
||||||
@echo === DEVELOPMENT ===
|
@echo "=== DEVELOPMENT ==="
|
||||||
@echo dev - Start API development server
|
@echo " dev - Start API development server"
|
||||||
@echo dev-full - Start API and documentation servers
|
@echo ""
|
||||||
@echo.
|
@echo "=== DATABASE ==="
|
||||||
@echo === DATABASE ===
|
@echo " migrate-create message=\"msg\" - Create new migration"
|
||||||
@echo migrate-create message="msg" - Create new migration
|
@echo " migrate-up - Apply pending migrations"
|
||||||
@echo migrate-up - Apply pending migrations
|
@echo " migrate-down - Rollback last migration"
|
||||||
@echo migrate-down - Rollback last migration
|
@echo " migrate-status - Show migration status"
|
||||||
@echo migrate-status - Show migration status
|
@echo " init-prod - Initialize production essentials"
|
||||||
@echo backup-db - Backup database
|
@echo " seed-demo - Seed demo data (3 vendors)"
|
||||||
@echo.
|
@echo " seed-demo-minimal - Seed minimal demo (1 vendor)"
|
||||||
@echo === TESTING ===
|
@echo " seed-demo-reset - DELETE ALL and reseed"
|
||||||
@echo test - Run all tests
|
@echo " db-setup - Full dev setup (migrate + init + seed)"
|
||||||
@echo test-coverage - Run tests with coverage
|
@echo " backup-db - Backup database"
|
||||||
@echo test-fast - Run fast tests only
|
@echo ""
|
||||||
@echo.
|
@echo "=== TESTING ==="
|
||||||
@echo === CODE QUALITY ===
|
@echo " test - Run all tests"
|
||||||
@echo format - Format code (black + isort)
|
@echo " test-coverage - Run tests with coverage"
|
||||||
@echo lint - Run linting (ruff + mypy)
|
@echo " test-fast - Run fast tests only"
|
||||||
@echo lint-flake8 - Run linting (flake8 + mypy - alternative)
|
@echo ""
|
||||||
@echo check - Format + lint
|
@echo "=== CODE QUALITY ==="
|
||||||
@echo ci - Full CI pipeline
|
@echo " format - Format code (black + isort)"
|
||||||
@echo qa - Quality assurance (format, lint, test, docs check)
|
@echo " lint - Run linting (ruff + mypy)"
|
||||||
@echo.
|
@echo " check - Format + lint"
|
||||||
@echo === DOCUMENTATION ===
|
@echo " ci - Full CI pipeline"
|
||||||
@echo docs-serve - Start documentation server
|
@echo " qa - Quality assurance"
|
||||||
@echo docs-build - Build documentation
|
@echo ""
|
||||||
@echo.
|
@echo "=== DOCUMENTATION ==="
|
||||||
@echo === DOCKER ===
|
@echo " docs-serve - Start documentation server"
|
||||||
@echo docker-build - Build Docker containers
|
@echo " docs-build - Build documentation"
|
||||||
@echo docker-up - Start Docker containers
|
@echo ""
|
||||||
@echo docker-down - Stop Docker containers
|
@echo "=== DOCKER ==="
|
||||||
@echo docker-restart - Restart Docker containers
|
@echo " docker-build - Build Docker containers"
|
||||||
@echo.
|
@echo " docker-up - Start Docker containers"
|
||||||
@echo === DEPLOYMENT ===
|
@echo " docker-down - Stop Docker containers"
|
||||||
@echo deploy-staging - Deploy to staging environment
|
@echo ""
|
||||||
@echo deploy-prod - Deploy to production environment
|
@echo "=== UTILITIES ==="
|
||||||
@echo.
|
@echo " clean - Clean build artifacts"
|
||||||
@echo === UTILITIES ===
|
@echo " check-env - Check Python environment and OS"
|
||||||
@echo clean - Clean build artifacts
|
@echo ""
|
||||||
@echo verify-setup - Verify project setup
|
@echo "=== DAILY WORKFLOW ==="
|
||||||
@echo check-env - Check Python and virtual environment
|
@echo " make setup # Initial setup"
|
||||||
@echo.
|
@echo " make dev # Start development"
|
||||||
@echo === DAILY WORKFLOW ===
|
@echo " make migrate-create message=\"feature\" # Create migration"
|
||||||
@echo make dev # Start development
|
@echo " make migrate-up # Apply migration"
|
||||||
@echo make migrate-create message="feature" # Create migration
|
@echo " make test # Run tests"
|
||||||
@echo make migrate-up # Apply migration
|
|
||||||
@echo make test # Run tests
|
|
||||||
|
|
||||||
help-db:
|
help-db:
|
||||||
@echo === DATABASE COMMANDS ===
|
@echo "=== DATABASE COMMANDS ==="
|
||||||
@echo migrate-create message="description" - Create auto-generated migration
|
@echo ""
|
||||||
@echo migrate-create-manual message="desc" - Create empty migration template
|
@echo "MIGRATIONS:"
|
||||||
@echo migrate-up - Apply all pending migrations
|
@echo " migrate-create message=\"description\" - Create auto-generated migration"
|
||||||
@echo migrate-down - Rollback last migration
|
@echo " migrate-create-manual message=\"desc\" - Create empty migration template"
|
||||||
@echo migrate-status - Show current status and history
|
@echo " migrate-up - Apply all pending migrations"
|
||||||
@echo backup-db - Create database backup
|
@echo " migrate-down - Rollback last migration"
|
||||||
@echo.
|
@echo " migrate-status - Show current status and history"
|
||||||
@echo TYPICAL WORKFLOW:
|
@echo ""
|
||||||
@echo 1. Edit your SQLAlchemy models
|
@echo "INITIALIZATION:"
|
||||||
@echo 2. make migrate-create message="add_new_feature"
|
@echo " init-prod - Create admin user + settings (SAFE for production)"
|
||||||
@echo 3. Review the generated migration file
|
@echo ""
|
||||||
@echo 4. make migrate-up
|
@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,
|
products,
|
||||||
orders,
|
orders,
|
||||||
customers,
|
customers,
|
||||||
teams,
|
team,
|
||||||
inventory,
|
inventory,
|
||||||
marketplace,
|
marketplace,
|
||||||
payments,
|
payments,
|
||||||
@@ -55,7 +55,7 @@ router.include_router(settings.router, tags=["vendor-settings"])
|
|||||||
router.include_router(products.router, tags=["vendor-products"])
|
router.include_router(products.router, tags=["vendor-products"])
|
||||||
router.include_router(orders.router, tags=["vendor-orders"])
|
router.include_router(orders.router, tags=["vendor-orders"])
|
||||||
router.include_router(customers.router, tags=["vendor-customers"])
|
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(inventory.router, tags=["vendor-inventory"])
|
||||||
router.include_router(marketplace.router, tags=["vendor-marketplace"])
|
router.include_router(marketplace.router, tags=["vendor-marketplace"])
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,35 @@
|
|||||||
# app/core/config.py
|
# app/core/config.py
|
||||||
"""Summary description ....
|
"""
|
||||||
|
Application configuration using Pydantic Settings.
|
||||||
|
|
||||||
This module provides classes and functions for:
|
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 typing import List, Optional
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
from pydantic_settings import \
|
|
||||||
BaseSettings # This is the correct import for Pydantic v2
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(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
|
Environment detection is delegated to app.core.environment.
|
||||||
project_name: str = "Ecommerce Backend API with Marketplace Support"
|
This class focuses on configuration values only.
|
||||||
# Documentation
|
"""
|
||||||
documentation_url: str = "http://localhost:8001" # Development default
|
|
||||||
|
# =============================================================================
|
||||||
|
# PROJECT INFORMATION
|
||||||
|
# =============================================================================
|
||||||
|
project_name: str = "Wizamart - Multi-Vendor Marketplace Platform"
|
||||||
|
version: str = "2.2.0"
|
||||||
|
|
||||||
# Clean description without HTML
|
# Clean description without HTML
|
||||||
description: str = """
|
description: str = """
|
||||||
@@ -34,49 +44,221 @@ class Settings(BaseSettings):
|
|||||||
**Documentation:** Visit /documentation for complete guides
|
**Documentation:** Visit /documentation for complete guides
|
||||||
**API Testing:** Use /docs for interactive API exploration
|
**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_secret_key: str = "change-this-in-production"
|
||||||
jwt_expire_hours: int = 24
|
jwt_expire_hours: int = 24
|
||||||
jwt_expire_minutes: int = 30
|
jwt_expire_minutes: int = 30
|
||||||
|
|
||||||
# Middleware
|
# =============================================================================
|
||||||
allowed_hosts: List[str] = ["*"] # Configure for production
|
# API SERVER
|
||||||
|
# =============================================================================
|
||||||
# API
|
|
||||||
api_host: str = "0.0.0.0"
|
api_host: str = "0.0.0.0"
|
||||||
api_port: int = 8000
|
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 Limiting
|
||||||
rate_limit_enabled: bool = True
|
rate_limit_enabled: bool = True
|
||||||
rate_limit_requests: int = 100
|
rate_limit_requests: int = 100
|
||||||
rate_limit_window: int = 3600
|
rate_limit_window: int = 3600
|
||||||
|
|
||||||
# Logging
|
# =============================================================================
|
||||||
|
# LOGGING
|
||||||
|
# =============================================================================
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
log_file: Optional[str] = None
|
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
|
# Custom domain features
|
||||||
allow_custom_domains: bool = True # Enable/disable custom domains
|
allow_custom_domains: bool = True
|
||||||
require_domain_verification: bool = True # Require DNS verification
|
require_domain_verification: bool = True
|
||||||
|
|
||||||
# SSL/TLS configuration for custom domains
|
# SSL/TLS configuration for custom domains
|
||||||
ssl_provider: str = "letsencrypt" # or "cloudflare", "manual"
|
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
|
||||||
dns_verification_prefix: str = "_wizamart-verify"
|
dns_verification_prefix: str = "_wizamart-verify"
|
||||||
dns_verification_ttl: int = 3600
|
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()
|
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 datetime import datetime
|
||||||
from typing import Optional, List
|
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)"
|
description="Custom permissions (overrides role preset)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@validator('role_name')
|
@field_validator('role_name')
|
||||||
def validate_role_name(cls, v):
|
def validate_role_name(cls, v):
|
||||||
"""Validate role name is in allowed presets."""
|
"""Validate role name is in allowed presets."""
|
||||||
if v is not None:
|
if v is not None:
|
||||||
@@ -83,7 +83,7 @@ class TeamMemberInvite(TeamMemberBase):
|
|||||||
)
|
)
|
||||||
return v.lower() if v else v
|
return v.lower() if v else v
|
||||||
|
|
||||||
@validator('custom_permissions')
|
@field_validator('custom_permissions')
|
||||||
def validate_custom_permissions(cls, v, values):
|
def validate_custom_permissions(cls, v, values):
|
||||||
"""Ensure either role_id/role_name OR custom_permissions is provided."""
|
"""Ensure either role_id/role_name OR custom_permissions is provided."""
|
||||||
if v is not None and len(v) > 0:
|
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)
|
first_name: str = Field(..., min_length=1, max_length=100)
|
||||||
last_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):
|
def validate_password_strength(cls, v):
|
||||||
"""Validate password meets minimum requirements."""
|
"""Validate password meets minimum requirements."""
|
||||||
if len(v) < 8:
|
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