data seed feature for demo and prod

This commit is contained in:
2025-11-15 20:57:39 +01:00
parent 41439eed09
commit e3ed4a3295
17 changed files with 4574 additions and 1793 deletions

109
.env
View File

@@ -1,53 +1,104 @@
# .env.example
# =============================================================================
# ENVIRONMENT CONFIGURATION
# =============================================================================
DEBUG=True
# Project information
PROJECT_NAME=Ecommerce Backend API with Marketplace Support
DESCRIPTION=Advanced product management system with JWT authentication
VERSION=0.0.1
# =============================================================================
# PROJECT INFORMATION
# =============================================================================
PROJECT_NAME=Wizamart - Multi-Vendor Marketplace Platform
DESCRIPTION=Multi-tenants multi-themes ecommerce application
VERSION=2.2.0
# Database Configuration
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
# For development, you can use SQLite:
# =============================================================================
# DATABASE CONFIGURATION
# =============================================================================
# For development (SQLite)
DATABASE_URL=sqlite:///./wizamart.db
# Documentation
# .env.development
DOCUMENTATION_URL=http://localhost:8001
# .env.production
# DOCUMENTATION_URL=https://yourdomain.com/docs
# .env.staging
# DOCUMENTATION_URL=https://staging-docs.yourdomain.com
# For production (PostgreSQL)
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
# JWT Configuration
# =============================================================================
# ADMIN INITIALIZATION
# =============================================================================
# These are used by init_production.py to create the platform admin
# ⚠️ CHANGE THESE IN PRODUCTION!
ADMIN_EMAIL=admin@wizamart.com
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
ADMIN_FIRST_NAME=Platform
ADMIN_LAST_NAME=Administrator
# =============================================================================
# JWT CONFIGURATION
# =============================================================================
JWT_SECRET_KEY=your-super-secret-jwt-key-change-in-production
JWT_EXPIRE_HOURS=24
JWT_EXPIRE_MINUTES=30
# API Configuration
# =============================================================================
# API SERVER
# =============================================================================
API_HOST=0.0.0.0
API_PORT=8000
DEBUG=False
# Rate Limiting
# =============================================================================
# DOCUMENTATION
# =============================================================================
# Development
DOCUMENTATION_URL=http://localhost:8001
# Staging
# DOCUMENTATION_URL=https://staging-docs.wizamart.com
# Production
# DOCUMENTATION_URL=https://docs.wizamart.com
# =============================================================================
# RATE LIMITING
# =============================================================================
RATE_LIMIT_ENABLED=True
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=3600
# Logging
# =============================================================================
# LOGGING
# =============================================================================
LOG_LEVEL=DEBUG
LOG_FILE=log/app.log
LOG_FILE=logs/app.log
# Platform domain configuration
PLATFORM_DOMAIN=platform.com # Your main platform domain
# =============================================================================
# PLATFORM DOMAIN CONFIGURATION
# =============================================================================
# Your main platform domain
PLATFORM_DOMAIN=wizamart.com
# Custom domain features
ALLOW_CUSTOM_DOMAINS=True # Enable/disable custom domains
REQUIRE_DOMAIN_VERIFICATION=True # Require DNS verification
# Enable/disable custom domains
ALLOW_CUSTOM_DOMAINS=True
# Require DNS verification
REQUIRE_DOMAIN_VERIFICATION=True
# SSL/TLS configuration for custom domains
SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
# SSL/TLS configuration
# "letsencrypt" or "cloudflare", "manual"
SSL_PROVIDER=letsencrypt
# Set to True if using automated SSL
AUTO_PROVISION_SSL=False
# DNS verification
DNS_VERIFICATION_PREFIX=_wizamart-verify
DNS_VERIFICATION_TTL=3600
DNS_VERIFICATION_TTL=3600
# =============================================================================
# PLATFORM LIMITS
# =============================================================================
MAX_VENDORS_PER_USER=5
MAX_TEAM_MEMBERS_PER_VENDOR=50
INVITATION_EXPIRY_DAYS=7
# =============================================================================
# DEMO/SEED DATA CONFIGURATION (Development only)
# =============================================================================
SEED_DEMO_VENDORS=3
SEED_CUSTOMERS_PER_VENDOR=15
SEED_PRODUCTS_PER_VENDOR=20
SEED_ORDERS_PER_VENDOR=10

347
Makefile
View File

@@ -1,7 +1,16 @@
# Multitenant ecommerce application Makefile (Windows Compatible)
# Wizamart Multi-Tenant E-Commerce Platform Makefile
# Cross-platform compatible (Windows & Linux)
.PHONY: install install-dev install-docs install-all dev test test-coverage lint format check docker-build docker-up docker-down clean help
# Detect if we're in a virtual environment and set Python path accordingly
# Detect OS
ifeq ($(OS),Windows_NT)
DETECTED_OS := Windows
else
DETECTED_OS := $(shell uname -s)
endif
# Set Python based on OS
PYTHON := python
PIP := pip
@@ -23,9 +32,9 @@ install-docs:
install-all: install install-dev install-test install-docs
setup: install-all migrate-up
@echo Development environment setup complete!
@echo Run 'make dev' to start development server
setup: install-all migrate-up init-prod
@echo "✅ Development environment setup complete!"
@echo "Run 'make dev' to start development server"
# =============================================================================
# DEVELOPMENT SERVERS
@@ -34,78 +43,96 @@ setup: install-all migrate-up
dev:
$(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
dev-with-docs:
@echo Starting API and documentation servers...
@start /B $(PYTHON) -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
@timeout /t 3 >nul
@$(PYTHON) -m mkdocs serve --dev-addr=0.0.0.0:8001
dev-full: dev-with-docs
@echo Development environment ready!
@echo API server: http://localhost:8000
@echo API docs: http://localhost:8000/docs
@echo Documentation: http://localhost:8001
# =============================================================================
# DATABASE MIGRATIONS
# =============================================================================
migrate-create:
@if [ "$(message)" = "" ]; then \
ifeq ($(DETECTED_OS),Windows)
@if "$(message)"=="" (echo Error: Please provide a message. Usage: make migrate-create message="your_description") else ($(PYTHON) -m alembic revision --autogenerate -m "$(message)")
else
@if [ -z "$(message)" ]; then \
echo "Error: Please provide a message. Usage: make migrate-create message=\"your_description\""; \
else \
$(PYTHON) -m alembic revision --autogenerate -m "$(message)"; \
fi
endif
migrate-create-manual:
ifeq ($(DETECTED_OS),Windows)
@if "$(message)"=="" (echo Error: Please provide a message. Usage: make migrate-create-manual message="your_description") else ($(PYTHON) -m alembic revision -m "$(message)")
else
@if [ -z "$(message)" ]; then \
echo "Error: Please provide a message. Usage: make migrate-create-manual message=\"your_description\""; \
else \
$(PYTHON) -m alembic revision -m "$(message)"; \
fi
endif
migrate-up:
@echo Running database migrations...
@echo "Running database migrations..."
$(PYTHON) -m alembic upgrade head
@echo Migrations completed successfully
@echo "✅ Migrations completed successfully"
migrate-down:
@echo Rolling back last migration...
@echo "Rolling back last migration..."
$(PYTHON) -m alembic downgrade -1
@echo Rollback completed
@echo "✅ Rollback completed"
migrate-status:
@echo Current migration status:
@echo "Current migration status:"
$(PYTHON) -m alembic current
@echo.
@echo Migration history:
@echo ""
@echo "Migration history:"
$(PYTHON) -m alembic history --verbose
# =============================================================================
# DATABASE INITIALIZATION & SEEDING
# =============================================================================
init-prod:
@echo "🔧 Initializing production database..."
$(PYTHON) scripts/init_production.py
@echo "✅ Production initialization completed"
# Demo data seeding - Cross-platform using Python to set environment
seed-demo:
@echo "🎪 Seeding demo data (normal mode)..."
ifeq ($(DETECTED_OS),Windows)
@set SEED_MODE=normal&& $(PYTHON) scripts/seed_demo.py
else
SEED_MODE=normal $(PYTHON) scripts/seed_demo.py
endif
@echo "✅ Demo seeding completed"
seed-demo-minimal:
@echo "🎪 Seeding demo data (minimal mode - 1 vendor only)..."
ifeq ($(DETECTED_OS),Windows)
@set SEED_MODE=minimal&& $(PYTHON) scripts/seed_demo.py
else
SEED_MODE=minimal $(PYTHON) scripts/seed_demo.py
endif
@echo "✅ Minimal demo seeding completed"
seed-demo-reset:
@echo "⚠️ WARNING: This will DELETE ALL existing data!"
ifeq ($(DETECTED_OS),Windows)
@set SEED_MODE=reset&& $(PYTHON) scripts/seed_demo.py
else
SEED_MODE=reset $(PYTHON) scripts/seed_demo.py
endif
db-setup: migrate-up init-prod seed-demo
@echo "✅ Database setup complete!"
@echo "Run 'make dev' to start development server"
db-reset: migrate-down migrate-up seed-demo-reset
@echo "✅ Database completely reset!"
backup-db:
@echo Creating database backup...
@echo "Creating database backup..."
@$(PYTHON) scripts/backup_database.py
# Add these commands to the DATABASE section after backup-db:
seed:
@echo Seeding database with comprehensive test data...
$(PYTHON) scripts/seed_database.py
@echo Seeding completed successfully
seed-minimal:
@echo Seeding database with minimal data (admin + 1 vendor)...
$(PYTHON) scripts/seed_database.py --minimal
@echo Minimal seeding completed
seed-reset:
@echo WARNING: This will DELETE ALL existing data!
$(PYTHON) scripts/seed_database.py --reset
@echo Database reset and seeded
# Complete database setup (migrate + seed)
db-setup: migrate-up seed
@echo Database setup complete!
@echo Run 'make dev' to start development server
db-reset: migrate-down migrate-up seed-reset
@echo Database completely reset!
# =============================================================================
# TESTING
# =============================================================================
@@ -142,54 +169,54 @@ test-inventory:
# =============================================================================
format:
@echo Running black...
@echo "Running black..."
$(PYTHON) -m black . --exclude venv
@echo Running isort...
@echo "Running isort..."
$(PYTHON) -m isort . --skip venv
lint:
@echo Running linting...
@echo "Running linting..."
$(PYTHON) -m ruff check . --exclude venv
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
# Alternative lint if still using flake8
lint-flake8:
@echo Running linting...
@echo "Running linting..."
$(PYTHON) -m flake8 . --max-line-length=120 --extend-ignore=E203,W503,I201,I100 --exclude=venv,__pycache__,.git
$(PYTHON) -m mypy . --ignore-missing-imports --exclude venv
# Combined format and lint
check: format lint
# Combined test with coverage and linting
ci: format lint test-coverage
# Quality assurance workflow
qa: format lint test-coverage docs-check
@echo Quality assurance checks completed!
@echo "Quality assurance checks completed!"
# =============================================================================
# DOCUMENTATION
# =============================================================================
docs-serve:
@echo Starting documentation server...
@echo "Starting documentation server..."
$(PYTHON) -m mkdocs serve --dev-addr=0.0.0.0:8001
docs-build:
@echo Building documentation...
@echo "Building documentation..."
$(PYTHON) -m mkdocs build --clean --strict
docs-deploy:
@echo Deploying documentation...
@echo "Deploying documentation..."
$(PYTHON) -m mkdocs gh-deploy --clean
docs-clean:
ifeq ($(DETECTED_OS),Windows)
@if exist site rmdir /s /q site
@echo Documentation build files cleaned!
else
@rm -rf site
endif
@echo "Documentation build files cleaned!"
docs-check:
@echo Checking documentation for issues...
@echo "Checking documentation for issues..."
$(PYTHON) -m mkdocs build --strict --verbose
# =============================================================================
@@ -222,30 +249,43 @@ deploy-prod: migrate-up
# =============================================================================
clean:
ifeq ($(DETECTED_OS),Windows)
@if exist htmlcov rmdir /s /q htmlcov
@if exist .pytest_cache rmdir /s /q .pytest_cache
@if exist .coverage del .coverage
@if exist .mypy_cache rmdir /s /q .mypy_cache
@for /d /r . %%d in (__pycache__) do @if exist "%%d" rmdir /s /q "%%d"
@del /s /q *.pyc 2>nul || echo No .pyc files found
else
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
@find . -type f -name "*.pyc" -delete 2>/dev/null || true
@rm -rf htmlcov .pytest_cache .coverage .mypy_cache 2>/dev/null || true
endif
@echo "Cleaned up build artifacts"
verify-setup:
@echo Running setup verification...
@echo "Running setup verification..."
@$(PYTHON) scripts/verify_setup.py
# Check Python and virtual environment
check-env:
@echo Checking Python environment...
@echo Python version:
@echo "Checking Python environment..."
@echo "Detected OS: $(DETECTED_OS)"
@echo ""
@echo "Python version:"
@$(PYTHON) --version
@echo.
@echo Python location:
@echo ""
ifeq ($(DETECTED_OS),Windows)
@echo "Python location:"
@where $(PYTHON)
@echo.
@echo Virtual environment active:
else
@echo "Python location:"
@which $(PYTHON)
endif
@echo ""
@echo "Virtual environment active:"
@$(PYTHON) -c "import sys; print('YES' if hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) else 'NO')"
@echo.
@echo Python executable:
@echo ""
@echo "Python executable:"
@$(PYTHON) -c "import sys; print(sys.executable)"
# =============================================================================
@@ -253,75 +293,92 @@ check-env:
# =============================================================================
help:
@echo LetzShop API Development Commands
@echo.
@echo === SETUP ===
@echo install - Install production dependencies
@echo install-test - Install test dependencies only
@echo install-dev - Install development dependencies
@echo install-all - Install all dependencies
@echo setup - Complete development setup
@echo.
@echo === DEVELOPMENT ===
@echo dev - Start API development server
@echo dev-full - Start API and documentation servers
@echo.
@echo === DATABASE ===
@echo migrate-create message="msg" - Create new migration
@echo migrate-up - Apply pending migrations
@echo migrate-down - Rollback last migration
@echo migrate-status - Show migration status
@echo backup-db - Backup database
@echo.
@echo === TESTING ===
@echo test - Run all tests
@echo test-coverage - Run tests with coverage
@echo test-fast - Run fast tests only
@echo.
@echo === CODE QUALITY ===
@echo format - Format code (black + isort)
@echo lint - Run linting (ruff + mypy)
@echo lint-flake8 - Run linting (flake8 + mypy - alternative)
@echo check - Format + lint
@echo ci - Full CI pipeline
@echo qa - Quality assurance (format, lint, test, docs check)
@echo.
@echo === DOCUMENTATION ===
@echo docs-serve - Start documentation server
@echo docs-build - Build documentation
@echo.
@echo === DOCKER ===
@echo docker-build - Build Docker containers
@echo docker-up - Start Docker containers
@echo docker-down - Stop Docker containers
@echo docker-restart - Restart Docker containers
@echo.
@echo === DEPLOYMENT ===
@echo deploy-staging - Deploy to staging environment
@echo deploy-prod - Deploy to production environment
@echo.
@echo === UTILITIES ===
@echo clean - Clean build artifacts
@echo verify-setup - Verify project setup
@echo check-env - Check Python and virtual environment
@echo.
@echo === DAILY WORKFLOW ===
@echo make dev # Start development
@echo make migrate-create message="feature" # Create migration
@echo make migrate-up # Apply migration
@echo make test # Run tests
@echo "Wizamart Platform Development Commands"
@echo ""
@echo "=== SETUP ==="
@echo " install - Install production dependencies"
@echo " install-test - Install test dependencies only"
@echo " install-dev - Install development dependencies"
@echo " install-all - Install all dependencies"
@echo " setup - Complete development setup"
@echo ""
@echo "=== DEVELOPMENT ==="
@echo " dev - Start API development server"
@echo ""
@echo "=== DATABASE ==="
@echo " migrate-create message=\"msg\" - Create new migration"
@echo " migrate-up - Apply pending migrations"
@echo " migrate-down - Rollback last migration"
@echo " migrate-status - Show migration status"
@echo " init-prod - Initialize production essentials"
@echo " seed-demo - Seed demo data (3 vendors)"
@echo " seed-demo-minimal - Seed minimal demo (1 vendor)"
@echo " seed-demo-reset - DELETE ALL and reseed"
@echo " db-setup - Full dev setup (migrate + init + seed)"
@echo " backup-db - Backup database"
@echo ""
@echo "=== TESTING ==="
@echo " test - Run all tests"
@echo " test-coverage - Run tests with coverage"
@echo " test-fast - Run fast tests only"
@echo ""
@echo "=== CODE QUALITY ==="
@echo " format - Format code (black + isort)"
@echo " lint - Run linting (ruff + mypy)"
@echo " check - Format + lint"
@echo " ci - Full CI pipeline"
@echo " qa - Quality assurance"
@echo ""
@echo "=== DOCUMENTATION ==="
@echo " docs-serve - Start documentation server"
@echo " docs-build - Build documentation"
@echo ""
@echo "=== DOCKER ==="
@echo " docker-build - Build Docker containers"
@echo " docker-up - Start Docker containers"
@echo " docker-down - Stop Docker containers"
@echo ""
@echo "=== UTILITIES ==="
@echo " clean - Clean build artifacts"
@echo " check-env - Check Python environment and OS"
@echo ""
@echo "=== DAILY WORKFLOW ==="
@echo " make setup # Initial setup"
@echo " make dev # Start development"
@echo " make migrate-create message=\"feature\" # Create migration"
@echo " make migrate-up # Apply migration"
@echo " make test # Run tests"
help-db:
@echo === DATABASE COMMANDS ===
@echo migrate-create message="description" - Create auto-generated migration
@echo migrate-create-manual message="desc" - Create empty migration template
@echo migrate-up - Apply all pending migrations
@echo migrate-down - Rollback last migration
@echo migrate-status - Show current status and history
@echo backup-db - Create database backup
@echo.
@echo TYPICAL WORKFLOW:
@echo 1. Edit your SQLAlchemy models
@echo 2. make migrate-create message="add_new_feature"
@echo 3. Review the generated migration file
@echo 4. make migrate-up
@echo "=== DATABASE COMMANDS ==="
@echo ""
@echo "MIGRATIONS:"
@echo " migrate-create message=\"description\" - Create auto-generated migration"
@echo " migrate-create-manual message=\"desc\" - Create empty migration template"
@echo " migrate-up - Apply all pending migrations"
@echo " migrate-down - Rollback last migration"
@echo " migrate-status - Show current status and history"
@echo ""
@echo "INITIALIZATION:"
@echo " init-prod - Create admin user + settings (SAFE for production)"
@echo ""
@echo "DEMO DATA (Development only):"
@echo " seed-demo - Create 3 demo vendors with data"
@echo " seed-demo-minimal - Create 1 demo vendor only"
@echo " seed-demo-reset - DELETE ALL data and reseed (DANGEROUS!)"
@echo ""
@echo "WORKFLOWS:"
@echo " db-setup - Complete dev setup (migrate + init + seed)"
@echo " db-reset - Nuclear option: rollback + reset + reseed"
@echo ""
@echo "TYPICAL FIRST-TIME SETUP:"
@echo " 1. make migrate-up # Apply migrations"
@echo " 2. make init-prod # Create admin user"
@echo " 3. make seed-demo # Add demo data"
@echo " 4. make dev # Start developing"
@echo ""
@echo "PRODUCTION SETUP:"
@echo " 1. Set ENV=production or ENVIRONMENT=production"
@echo " 2. make migrate-up"
@echo " 3. make init-prod (with secure credentials in .env)"
@echo " 4. Create vendors via admin panel"

View File

@@ -22,7 +22,7 @@ from . import (
products,
orders,
customers,
teams,
team,
inventory,
marketplace,
payments,
@@ -55,7 +55,7 @@ router.include_router(settings.router, tags=["vendor-settings"])
router.include_router(products.router, tags=["vendor-products"])
router.include_router(orders.router, tags=["vendor-orders"])
router.include_router(customers.router, tags=["vendor-customers"])
router.include_router(teams.router, tags=["vendor-teams"])
router.include_router(team.router, tags=["vendor-team"])
router.include_router(inventory.router, tags=["vendor-inventory"])
router.include_router(marketplace.router, tags=["vendor-marketplace"])

View File

@@ -1,25 +1,35 @@
# app/core/config.py
"""Summary description ....
"""
Application configuration using Pydantic Settings.
This module provides classes and functions for:
- ....
- ....
- ....
- Configuration management via environment variables
- Database settings
- JWT and authentication configuration
- Platform domain and multi-tenancy settings
- Admin initialization settings
Note: Environment detection is handled by app.core.environment module.
This module focuses purely on configuration storage and validation.
"""
from typing import List, Optional
from pydantic_settings import \
BaseSettings # This is the correct import for Pydantic v2
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
"""Settings class inheriting from BaseSettings that allows values to be overridden by environment variables."""
"""
Settings class for application configuration.
# Project information
project_name: str = "Ecommerce Backend API with Marketplace Support"
# Documentation
documentation_url: str = "http://localhost:8001" # Development default
Environment detection is delegated to app.core.environment.
This class focuses on configuration values only.
"""
# =============================================================================
# PROJECT INFORMATION
# =============================================================================
project_name: str = "Wizamart - Multi-Vendor Marketplace Platform"
version: str = "2.2.0"
# Clean description without HTML
description: str = """
@@ -34,49 +44,221 @@ class Settings(BaseSettings):
**Documentation:** Visit /documentation for complete guides
**API Testing:** Use /docs for interactive API exploration
"""
version: str = "2.2.0"
# Database
database_url: str = "sqlite:///./ecommerce.db"
# =============================================================================
# DATABASE
# =============================================================================
database_url: str = "sqlite:///./wizamart.db"
# JWT
# =============================================================================
# ADMIN INITIALIZATION (for init_production.py)
# =============================================================================
admin_email: str = "admin@wizamart.com"
admin_username: str = "admin"
admin_password: str = "admin123" # CHANGE IN PRODUCTION!
admin_first_name: str = "Platform"
admin_last_name: str = "Administrator"
# =============================================================================
# JWT AUTHENTICATION
# =============================================================================
jwt_secret_key: str = "change-this-in-production"
jwt_expire_hours: int = 24
jwt_expire_minutes: int = 30
# Middleware
allowed_hosts: List[str] = ["*"] # Configure for production
# API
# =============================================================================
# API SERVER
# =============================================================================
api_host: str = "0.0.0.0"
api_port: int = 8000
debug: bool = False
debug: bool = True
# =============================================================================
# DOCUMENTATION
# =============================================================================
documentation_url: str = "http://localhost:8001"
# =============================================================================
# MIDDLEWARE & SECURITY
# =============================================================================
allowed_hosts: List[str] = ["*"] # Configure for production
# Rate Limiting
rate_limit_enabled: bool = True
rate_limit_requests: int = 100
rate_limit_window: int = 3600
# Logging
# =============================================================================
# LOGGING
# =============================================================================
log_level: str = "INFO"
log_file: Optional[str] = None
# Platform domain configuration
platform_domain: str = "platform.com" # Your main platform domain
# =============================================================================
# PLATFORM DOMAIN CONFIGURATION
# =============================================================================
platform_domain: str = "wizamart.com"
# Custom domain features
allow_custom_domains: bool = True # Enable/disable custom domains
require_domain_verification: bool = True # Require DNS verification
allow_custom_domains: bool = True
require_domain_verification: bool = True
# SSL/TLS configuration for custom domains
ssl_provider: str = "letsencrypt" # or "cloudflare", "manual"
auto_provision_ssl: bool = False # Set to True if using automated SSL
auto_provision_ssl: bool = False
# DNS verification
dns_verification_prefix: str = "_wizamart-verify"
dns_verification_ttl: int = 3600
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
# =============================================================================
# PLATFORM LIMITS
# =============================================================================
max_vendors_per_user: int = 5
max_team_members_per_vendor: int = 50
invitation_expiry_days: int = 7
# =============================================================================
# DEMO/SEED DATA CONFIGURATION
# =============================================================================
# Controls for demo data seeding
seed_demo_vendors: int = 3 # Number of demo vendors to create
seed_customers_per_vendor: int = 15 # Customers per vendor
seed_products_per_vendor: int = 20 # Products per vendor
seed_orders_per_vendor: int = 10 # Orders per vendor
model_config = {"env_file": ".env"}
# Singleton settings instance
settings = Settings()
# =============================================================================
# ENVIRONMENT UTILITIES - Module-level functions
# =============================================================================
# Import environment detection utilities
from app.core.environment import (
get_environment,
is_development,
is_production,
is_staging,
should_use_secure_cookies
)
def get_current_environment() -> str:
"""
Get current environment.
Convenience function that delegates to app.core.environment.
Use this when you need just the environment string.
"""
return get_environment()
def is_production_environment() -> bool:
"""
Check if running in production.
Convenience function that delegates to app.core.environment.
Use this for production-specific logic.
"""
return is_production()
def is_development_environment() -> bool:
"""
Check if running in development.
Convenience function that delegates to app.core.environment.
Use this for development-specific logic.
"""
return is_development()
def is_staging_environment() -> bool:
"""
Check if running in staging.
Convenience function that delegates to app.core.environment.
Use this for staging-specific logic.
"""
return is_staging()
# =============================================================================
# VALIDATION FUNCTIONS
# =============================================================================
def validate_production_settings() -> List[str]:
"""
Validate settings for production environment.
Returns:
List of warning messages if configuration is insecure
"""
warnings = []
if is_production():
# Check for default/insecure values
if settings.admin_password == "admin123":
warnings.append("⚠️ Using default admin password in production!")
if settings.jwt_secret_key == "change-this-in-production":
warnings.append("⚠️ Using default JWT secret key in production!")
if settings.debug:
warnings.append("⚠️ Debug mode enabled in production!")
if "*" in settings.allowed_hosts:
warnings.append("⚠️ ALLOWED_HOSTS is set to wildcard (*) in production!")
return warnings
def print_environment_info():
"""Print current environment configuration."""
print("\n" + "=" * 70)
print(f" ENVIRONMENT: {get_environment().upper()}")
print("=" * 70)
print(f" Database: {settings.database_url}")
print(f" Debug mode: {settings.debug}")
print(f" API port: {settings.api_port}")
print(f" Platform: {settings.platform_domain}")
print(f" Secure cookies: {should_use_secure_cookies()}")
print("=" * 70 + "\n")
# Show warnings if in production
if is_production():
warnings = validate_production_settings()
if warnings:
print("\n⚠️ PRODUCTION WARNINGS:")
for warning in warnings:
print(f" {warning}")
print()
# =============================================================================
# PUBLIC API
# =============================================================================
__all__ = [
# Settings singleton
'settings',
# Environment detection (re-exported from app.core.environment)
'get_environment',
'is_development',
'is_production',
'is_staging',
'should_use_secure_cookies',
# Convenience functions
'get_current_environment',
'is_production_environment',
'is_development_environment',
'is_staging_environment',
# Validation
'validate_production_settings',
'print_environment_info',
]

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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.

View File

@@ -1,3 +0,0 @@
@echo off
call venv\Scripts\activate.bat
C:\ProgramData\chocolatey\bin\make.exe %*

View File

@@ -11,7 +11,7 @@ This module defines request/response schemas for:
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel, EmailStr, Field, validator
from pydantic import BaseModel, EmailStr, Field, field_validator
# ============================================================================
@@ -72,7 +72,7 @@ class TeamMemberInvite(TeamMemberBase):
description="Custom permissions (overrides role preset)"
)
@validator('role_name')
@field_validator('role_name')
def validate_role_name(cls, v):
"""Validate role name is in allowed presets."""
if v is not None:
@@ -83,7 +83,7 @@ class TeamMemberInvite(TeamMemberBase):
)
return v.lower() if v else v
@validator('custom_permissions')
@field_validator('custom_permissions')
def validate_custom_permissions(cls, v, values):
"""Ensure either role_id/role_name OR custom_permissions is provided."""
if v is not None and len(v) > 0:
@@ -151,7 +151,7 @@ class InvitationAccept(BaseModel):
first_name: str = Field(..., min_length=1, max_length=100)
last_name: str = Field(..., min_length=1, max_length=100)
@validator('password')
@field_validator('password')
def validate_password_strength(cls, v):
"""Validate password meets minimum requirements."""
if len(v) < 8:

View File

@@ -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
View 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
View 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()

View File

@@ -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
View 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()