diff --git a/.env b/.env index 708119f1..4b92403e 100644 --- a/.env +++ b/.env @@ -6,9 +6,9 @@ DESCRIPTION=Advanced product management system with JWT authentication VERSION=0.0.1 # Database Configuration -# DATABASE_URL=postgresql://username:password@localhost:5432/ecommerce_db +# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db # For development, you can use SQLite: -DATABASE_URL=sqlite:///./ecommerce.db +DATABASE_URL=sqlite:///./wizamart.db # Documentation # .env.development @@ -49,5 +49,5 @@ SSL_PROVIDER=letsencrypt # or "cloudflare", "manual" AUTO_PROVISION_SSL=False # Set to True if using automated SSL # DNS verification -DNS_VERIFICATION_PREFIX=_letzshop-verify +DNS_VERIFICATION_PREFIX=_wizamart-verify DNS_VERIFICATION_TTL=3600 \ No newline at end of file diff --git a/.env.example b/.env.example index 708119f1..4b92403e 100644 --- a/.env.example +++ b/.env.example @@ -6,9 +6,9 @@ DESCRIPTION=Advanced product management system with JWT authentication VERSION=0.0.1 # Database Configuration -# DATABASE_URL=postgresql://username:password@localhost:5432/ecommerce_db +# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db # For development, you can use SQLite: -DATABASE_URL=sqlite:///./ecommerce.db +DATABASE_URL=sqlite:///./wizamart.db # Documentation # .env.development @@ -49,5 +49,5 @@ SSL_PROVIDER=letsencrypt # or "cloudflare", "manual" AUTO_PROVISION_SSL=False # Set to True if using automated SSL # DNS verification -DNS_VERIFICATION_PREFIX=_letzshop-verify +DNS_VERIFICATION_PREFIX=_wizamart-verify DNS_VERIFICATION_TTL=3600 \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py index 99cc7b75..2e1dbbe2 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,4 +1,15 @@ # alembic/env.py +""" +Alembic migration environment configuration. + +This file is responsible for: +1. Importing ALL database models so Alembic can detect schema changes +2. Configuring the database connection +3. Running migrations in online/offline mode + +CRITICAL: Every model in models/database/__init__.py must be imported here! +""" + import os import sys from logging.config import fileConfig @@ -12,93 +23,160 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__))) from app.core.config import settings from models.database.base import Base -# === IMPORTANT: Import all your DATABASE models here === -# Only import SQLAlchemy models, not Pydantic API models -print("[ALEMBIC] Importing database models...") +# ============================================================================ +# IMPORT ALL DATABASE MODELS +# ============================================================================ +# CRITICAL: Every model must be imported here so Alembic can detect tables! +# If a model is not imported, Alembic will not create/update its table. +# +# Models list must match: models/database/__init__.py +# ============================================================================ -# Core models +print("[ALEMBIC] Importing database models...") +print("=" * 70) + +# ---------------------------------------------------------------------------- +# ADMIN MODELS +# ---------------------------------------------------------------------------- +try: + from models.database.admin import ( + AdminAuditLog, + AdminNotification, + AdminSetting, + PlatformAlert, + AdminSession + ) + + print(" ✓ Admin models imported (5 models)") + print(" - AdminAuditLog") + print(" - AdminNotification") + print(" - AdminSetting") + print(" - PlatformAlert") + print(" - AdminSession") +except ImportError as e: + print(f" ✗ Admin models failed: {e}") + +# ---------------------------------------------------------------------------- +# USER MODEL +# ---------------------------------------------------------------------------- try: from models.database.user import User + print(" ✓ User model imported") except ImportError as e: print(f" ✗ User model failed: {e}") +# ---------------------------------------------------------------------------- +# VENDOR MODELS +# ---------------------------------------------------------------------------- try: from models.database.vendor import Vendor, VendorUser, Role - print(" ✓ Vendor models imported (Vendor, VendorUser, Role)") + + print(" ✓ Vendor models imported (3 models)") + print(" - Vendor") + print(" - VendorUser") + print(" - Role") except ImportError as e: print(f" ✗ Vendor models failed: {e}") try: from models.database.vendor_domain import VendorDomain + print(" ✓ VendorDomain model imported") except ImportError as e: print(f" ✗ VendorDomain model failed: {e}") try: from models.database.vendor_theme import VendorTheme + print(" ✓ VendorTheme model imported") except ImportError as e: print(f" ✗ VendorTheme model failed: {e}") -# Product models -try: - from models.database.marketplace_product import MarketplaceProduct - print(" ✓ MarketplaceProduct model imported") -except ImportError as e: - print(f" ✗ MarketplaceProduct model failed: {e}") - +# ---------------------------------------------------------------------------- +# PRODUCT MODELS +# ---------------------------------------------------------------------------- try: from models.database.product import Product + print(" ✓ Product model imported") except ImportError as e: print(f" ✗ Product model failed: {e}") -# Inventory +try: + from models.database.marketplace_product import MarketplaceProduct + + print(" ✓ MarketplaceProduct model imported") +except ImportError as e: + print(f" ✗ MarketplaceProduct model failed: {e}") + +# ---------------------------------------------------------------------------- +# INVENTORY MODEL +# ---------------------------------------------------------------------------- try: from models.database.inventory import Inventory + print(" ✓ Inventory model imported") except ImportError as e: print(f" ✗ Inventory model failed: {e}") -# Marketplace imports +# ---------------------------------------------------------------------------- +# MARKETPLACE IMPORT +# ---------------------------------------------------------------------------- try: from models.database.marketplace_import_job import MarketplaceImportJob + print(" ✓ MarketplaceImportJob model imported") except ImportError as e: print(f" ✗ MarketplaceImportJob model failed: {e}") -# Customer models +# ---------------------------------------------------------------------------- +# CUSTOMER MODELS +# ---------------------------------------------------------------------------- try: from models.database.customer import Customer, CustomerAddress - print(" ✓ Customer models imported (Customer, CustomerAddress)") + + print(" ✓ Customer models imported (2 models)") + print(" - Customer") + print(" - CustomerAddress") except ImportError as e: print(f" ✗ Customer models failed: {e}") -# Order models +# ---------------------------------------------------------------------------- +# ORDER MODELS +# ---------------------------------------------------------------------------- try: from models.database.order import Order, OrderItem - print(" ✓ Order models imported (Order, OrderItem)") + + print(" ✓ Order models imported (2 models)") + print(" - Order") + print(" - OrderItem") except ImportError as e: print(f" ✗ Order models failed: {e}") -# Payment models (if you have them) -try: - from models.database.payment import Payment - print(" ✓ Payment model imported") -except ImportError as e: - print(f" - Payment model not found (optional)") +# ============================================================================ +# SUMMARY +# ============================================================================ +print("=" * 70) +print(f"[ALEMBIC] Model import completed") +print(f"[ALEMBIC] Tables detected in metadata:") +print("=" * 70) -# Shipping models (if you have them) -try: - from models.database.shipping import ShippingAddress - print(" ✓ Shipping model imported") -except ImportError as e: - print(f" - Shipping model not found (optional)") +if Base.metadata.tables: + for i, table_name in enumerate(sorted(Base.metadata.tables.keys()), 1): + print(f" {i:2d}. {table_name}") + print("=" * 70) + print(f"[ALEMBIC] Total tables: {len(Base.metadata.tables)}") +else: + print(" ⚠️ WARNING: No tables found in metadata!") + print(" This usually means models are not being imported correctly.") -print(f"\n[ALEMBIC] Model import completed.") -print(f"[ALEMBIC] Tables found: {list(Base.metadata.tables.keys())}") -print(f"[ALEMBIC] Total tables to create: {len(Base.metadata.tables)}\n") +print("=" * 70) +print() + +# ============================================================================ +# ALEMBIC CONFIGURATION +# ============================================================================ # Alembic Config object config = context.config @@ -109,11 +187,20 @@ config.set_main_option("sqlalchemy.url", settings.database_url) if config.config_file_name is not None: fileConfig(config.config_file_name) +# Set target metadata from Base target_metadata = Base.metadata def run_migrations_offline() -> None: - """Run migrations in 'offline' mode.""" + """ + Run migrations in 'offline' mode. + + This configures the context with just a URL and not an Engine, + though an Engine is acceptable here as well. By skipping the Engine + creation we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the script output. + """ url = config.get_main_option("sqlalchemy.url") context.configure( url=url, @@ -127,7 +214,12 @@ def run_migrations_offline() -> None: def run_migrations_online() -> None: - """Run migrations in 'online' mode.""" + """ + Run migrations in 'online' mode. + + In this scenario we need to create an Engine and associate a connection + with the context. + """ connectable = engine_from_config( config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", @@ -135,12 +227,19 @@ def run_migrations_online() -> None: ) with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) + context.configure( + connection=connection, + target_metadata=target_metadata + ) with context.begin_transaction(): context.run_migrations() +# ============================================================================ +# MAIN EXECUTION +# ============================================================================ + if context.is_offline_mode(): run_migrations_offline() else: diff --git a/alembic/versions/6fe45d3d84c4_initial_schema_with_proper_relationships.py b/alembic/versions/4951b2e50581_initial_migration_all_tables.py similarity index 64% rename from alembic/versions/6fe45d3d84c4_initial_schema_with_proper_relationships.py rename to alembic/versions/4951b2e50581_initial_migration_all_tables.py index b7466a41..36b6fe27 100644 --- a/alembic/versions/6fe45d3d84c4_initial_schema_with_proper_relationships.py +++ b/alembic/versions/4951b2e50581_initial_migration_all_tables.py @@ -1,8 +1,8 @@ -"""initial_schema_with_proper_relationships +"""Initial migration - all tables -Revision ID: 6fe45d3d84c4 +Revision ID: 4951b2e50581 Revises: -Create Date: 2025-10-07 22:11:56.036486 +Create Date: 2025-10-27 22:28:33.137564 """ from typing import Sequence, Union @@ -12,7 +12,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = '6fe45d3d84c4' +revision: str = '4951b2e50581' down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -92,6 +92,112 @@ def upgrade() -> None: op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True) op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False) op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True) + op.create_table('admin_audit_logs', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('admin_user_id', sa.Integer(), nullable=False), + sa.Column('action', sa.String(length=100), nullable=False), + sa.Column('target_type', sa.String(length=50), nullable=False), + sa.Column('target_id', sa.String(length=100), nullable=False), + sa.Column('details', sa.JSON(), nullable=True), + sa.Column('ip_address', sa.String(length=45), nullable=True), + sa.Column('user_agent', sa.Text(), nullable=True), + sa.Column('request_id', sa.String(length=100), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['admin_user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_admin_audit_logs_action'), 'admin_audit_logs', ['action'], unique=False) + op.create_index(op.f('ix_admin_audit_logs_admin_user_id'), 'admin_audit_logs', ['admin_user_id'], unique=False) + op.create_index(op.f('ix_admin_audit_logs_id'), 'admin_audit_logs', ['id'], unique=False) + op.create_index(op.f('ix_admin_audit_logs_target_id'), 'admin_audit_logs', ['target_id'], unique=False) + op.create_index(op.f('ix_admin_audit_logs_target_type'), 'admin_audit_logs', ['target_type'], unique=False) + op.create_table('admin_notifications', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('type', sa.String(length=50), nullable=False), + sa.Column('priority', sa.String(length=20), nullable=True), + sa.Column('title', sa.String(length=200), nullable=False), + sa.Column('message', sa.Text(), nullable=False), + sa.Column('is_read', sa.Boolean(), nullable=True), + sa.Column('read_at', sa.DateTime(), nullable=True), + sa.Column('read_by_user_id', sa.Integer(), nullable=True), + sa.Column('action_required', sa.Boolean(), nullable=True), + sa.Column('action_url', sa.String(length=500), nullable=True), + sa.Column('notification_metadata', sa.JSON(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['read_by_user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_admin_notifications_action_required'), 'admin_notifications', ['action_required'], unique=False) + op.create_index(op.f('ix_admin_notifications_id'), 'admin_notifications', ['id'], unique=False) + op.create_index(op.f('ix_admin_notifications_is_read'), 'admin_notifications', ['is_read'], unique=False) + op.create_index(op.f('ix_admin_notifications_priority'), 'admin_notifications', ['priority'], unique=False) + op.create_index(op.f('ix_admin_notifications_type'), 'admin_notifications', ['type'], unique=False) + op.create_table('admin_sessions', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('admin_user_id', sa.Integer(), nullable=False), + sa.Column('session_token', sa.String(length=255), nullable=False), + sa.Column('ip_address', sa.String(length=45), nullable=False), + sa.Column('user_agent', sa.Text(), nullable=True), + sa.Column('login_at', sa.DateTime(), nullable=False), + sa.Column('last_activity_at', sa.DateTime(), nullable=False), + sa.Column('logout_at', sa.DateTime(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('logout_reason', sa.String(length=50), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['admin_user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_admin_sessions_admin_user_id'), 'admin_sessions', ['admin_user_id'], unique=False) + op.create_index(op.f('ix_admin_sessions_id'), 'admin_sessions', ['id'], unique=False) + op.create_index(op.f('ix_admin_sessions_is_active'), 'admin_sessions', ['is_active'], unique=False) + op.create_index(op.f('ix_admin_sessions_login_at'), 'admin_sessions', ['login_at'], unique=False) + op.create_index(op.f('ix_admin_sessions_session_token'), 'admin_sessions', ['session_token'], unique=True) + op.create_table('admin_settings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('key', sa.String(length=100), nullable=False), + sa.Column('value', sa.Text(), nullable=False), + sa.Column('value_type', sa.String(length=20), nullable=True), + sa.Column('category', sa.String(length=50), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('is_encrypted', sa.Boolean(), nullable=True), + sa.Column('is_public', sa.Boolean(), nullable=True), + sa.Column('last_modified_by_user_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['last_modified_by_user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_admin_settings_category'), 'admin_settings', ['category'], unique=False) + op.create_index(op.f('ix_admin_settings_id'), 'admin_settings', ['id'], unique=False) + op.create_index(op.f('ix_admin_settings_key'), 'admin_settings', ['key'], unique=True) + op.create_table('platform_alerts', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('alert_type', sa.String(length=50), nullable=False), + sa.Column('severity', sa.String(length=20), nullable=False), + sa.Column('title', sa.String(length=200), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('affected_vendors', sa.JSON(), nullable=True), + sa.Column('affected_systems', sa.JSON(), nullable=True), + sa.Column('is_resolved', sa.Boolean(), nullable=True), + sa.Column('resolved_at', sa.DateTime(), nullable=True), + sa.Column('resolved_by_user_id', sa.Integer(), nullable=True), + sa.Column('resolution_notes', sa.Text(), nullable=True), + sa.Column('auto_generated', sa.Boolean(), nullable=True), + sa.Column('occurrence_count', sa.Integer(), nullable=True), + sa.Column('first_occurred_at', sa.DateTime(), nullable=False), + sa.Column('last_occurred_at', sa.DateTime(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['resolved_by_user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_platform_alerts_alert_type'), 'platform_alerts', ['alert_type'], unique=False) + op.create_index(op.f('ix_platform_alerts_id'), 'platform_alerts', ['id'], unique=False) + op.create_index(op.f('ix_platform_alerts_is_resolved'), 'platform_alerts', ['is_resolved'], unique=False) + op.create_index(op.f('ix_platform_alerts_severity'), 'platform_alerts', ['severity'], unique=False) op.create_table('vendors', sa.Column('id', sa.Integer(), nullable=False), sa.Column('vendor_code', sa.String(), nullable=False), @@ -99,7 +205,6 @@ def upgrade() -> None: sa.Column('name', sa.String(), nullable=False), sa.Column('description', sa.Text(), nullable=True), sa.Column('owner_user_id', sa.Integer(), nullable=False), - sa.Column('theme_config', sa.JSON(), nullable=True), sa.Column('contact_email', sa.String(), nullable=True), sa.Column('contact_phone', sa.String(), nullable=True), sa.Column('website', sa.String(), nullable=True), @@ -203,6 +308,54 @@ def upgrade() -> None: sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_roles_id'), 'roles', ['id'], unique=False) + op.create_table('vendor_domains', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('vendor_id', sa.Integer(), nullable=False), + sa.Column('domain', sa.String(length=255), nullable=False), + sa.Column('is_primary', sa.Boolean(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('ssl_status', sa.String(length=50), nullable=True), + sa.Column('ssl_verified_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('verification_token', sa.String(length=100), nullable=True), + sa.Column('is_verified', sa.Boolean(), nullable=False), + sa.Column('verified_at', sa.DateTime(timezone=True), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('vendor_id', 'domain', name='uq_vendor_domain'), + sa.UniqueConstraint('verification_token') + ) + op.create_index('idx_domain_active', 'vendor_domains', ['domain', 'is_active'], unique=False) + op.create_index('idx_vendor_primary', 'vendor_domains', ['vendor_id', 'is_primary'], unique=False) + op.create_index(op.f('ix_vendor_domains_domain'), 'vendor_domains', ['domain'], unique=True) + op.create_index(op.f('ix_vendor_domains_id'), 'vendor_domains', ['id'], unique=False) + op.create_table('vendor_themes', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('vendor_id', sa.Integer(), nullable=False), + sa.Column('theme_name', sa.String(length=100), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=True), + sa.Column('colors', sa.JSON(), nullable=True), + sa.Column('font_family_heading', sa.String(length=100), nullable=True), + sa.Column('font_family_body', sa.String(length=100), nullable=True), + sa.Column('logo_url', sa.String(length=500), nullable=True), + sa.Column('logo_dark_url', sa.String(length=500), nullable=True), + sa.Column('favicon_url', sa.String(length=500), nullable=True), + sa.Column('banner_url', sa.String(length=500), nullable=True), + sa.Column('layout_style', sa.String(length=50), nullable=True), + sa.Column('header_style', sa.String(length=50), nullable=True), + sa.Column('product_card_style', sa.String(length=50), nullable=True), + sa.Column('custom_css', sa.Text(), nullable=True), + sa.Column('social_links', sa.JSON(), nullable=True), + sa.Column('meta_title_template', sa.String(length=200), nullable=True), + sa.Column('meta_description', sa.Text(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('vendor_id') + ) + op.create_index(op.f('ix_vendor_themes_id'), 'vendor_themes', ['id'], unique=False) op.create_table('customer_addresses', sa.Column('id', sa.Integer(), nullable=False), sa.Column('vendor_id', sa.Integer(), nullable=False), @@ -342,6 +495,13 @@ def downgrade() -> None: op.drop_table('inventory') op.drop_index(op.f('ix_customer_addresses_id'), table_name='customer_addresses') op.drop_table('customer_addresses') + op.drop_index(op.f('ix_vendor_themes_id'), table_name='vendor_themes') + op.drop_table('vendor_themes') + op.drop_index(op.f('ix_vendor_domains_id'), table_name='vendor_domains') + op.drop_index(op.f('ix_vendor_domains_domain'), table_name='vendor_domains') + op.drop_index('idx_vendor_primary', table_name='vendor_domains') + op.drop_index('idx_domain_active', table_name='vendor_domains') + op.drop_table('vendor_domains') op.drop_index(op.f('ix_roles_id'), table_name='roles') op.drop_table('roles') op.drop_index(op.f('ix_products_id'), table_name='products') @@ -363,6 +523,33 @@ def downgrade() -> None: op.drop_index(op.f('ix_vendors_subdomain'), table_name='vendors') op.drop_index(op.f('ix_vendors_id'), table_name='vendors') op.drop_table('vendors') + op.drop_index(op.f('ix_platform_alerts_severity'), table_name='platform_alerts') + op.drop_index(op.f('ix_platform_alerts_is_resolved'), table_name='platform_alerts') + op.drop_index(op.f('ix_platform_alerts_id'), table_name='platform_alerts') + op.drop_index(op.f('ix_platform_alerts_alert_type'), table_name='platform_alerts') + op.drop_table('platform_alerts') + op.drop_index(op.f('ix_admin_settings_key'), table_name='admin_settings') + op.drop_index(op.f('ix_admin_settings_id'), table_name='admin_settings') + op.drop_index(op.f('ix_admin_settings_category'), table_name='admin_settings') + op.drop_table('admin_settings') + op.drop_index(op.f('ix_admin_sessions_session_token'), table_name='admin_sessions') + op.drop_index(op.f('ix_admin_sessions_login_at'), table_name='admin_sessions') + op.drop_index(op.f('ix_admin_sessions_is_active'), table_name='admin_sessions') + op.drop_index(op.f('ix_admin_sessions_id'), table_name='admin_sessions') + op.drop_index(op.f('ix_admin_sessions_admin_user_id'), table_name='admin_sessions') + op.drop_table('admin_sessions') + op.drop_index(op.f('ix_admin_notifications_type'), table_name='admin_notifications') + op.drop_index(op.f('ix_admin_notifications_priority'), table_name='admin_notifications') + op.drop_index(op.f('ix_admin_notifications_is_read'), table_name='admin_notifications') + op.drop_index(op.f('ix_admin_notifications_id'), table_name='admin_notifications') + op.drop_index(op.f('ix_admin_notifications_action_required'), table_name='admin_notifications') + op.drop_table('admin_notifications') + op.drop_index(op.f('ix_admin_audit_logs_target_type'), table_name='admin_audit_logs') + op.drop_index(op.f('ix_admin_audit_logs_target_id'), table_name='admin_audit_logs') + op.drop_index(op.f('ix_admin_audit_logs_id'), table_name='admin_audit_logs') + op.drop_index(op.f('ix_admin_audit_logs_admin_user_id'), table_name='admin_audit_logs') + op.drop_index(op.f('ix_admin_audit_logs_action'), table_name='admin_audit_logs') + op.drop_table('admin_audit_logs') op.drop_index(op.f('ix_users_username'), table_name='users') op.drop_index(op.f('ix_users_id'), table_name='users') op.drop_index(op.f('ix_users_email'), table_name='users') diff --git a/alembic/versions/9189d3baaea1_add_vendor_and_role_tables_for_slice_1.py b/alembic/versions/9189d3baaea1_add_vendor_and_role_tables_for_slice_1.py deleted file mode 100644 index 86da8fee..00000000 --- a/alembic/versions/9189d3baaea1_add_vendor_and_role_tables_for_slice_1.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Add vendor and role tables for slice 1 - -Revision ID: 9189d3baaea1 -Revises: 6fe45d3d84c4 -Create Date: 2025-10-08 22:39:53.101668 - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = '9189d3baaea1' -down_revision: Union[str, None] = '6fe45d3d84c4' -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/__init__.py b/alembic/versions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/api/v1/admin/vendor_domains.py b/app/api/v1/admin/vendor_domains.py index b9d470b2..4f0a0c8c 100644 --- a/app/api/v1/admin/vendor_domains.py +++ b/app/api/v1/admin/vendor_domains.py @@ -266,14 +266,14 @@ def verify_domain_ownership( Verify domain ownership via DNS TXT record (Admin only). **Verification Process:** - 1. Queries DNS for TXT record: `_letzshop-verify.{domain}` + 1. Queries DNS for TXT record: `_wizamart-verify.{domain}` 2. Checks if verification token matches 3. If found, marks domain as verified **Requirements:** - Vendor must have added TXT record to their DNS - DNS propagation may take 5-15 minutes - - Record format: `_letzshop-verify.domain.com` TXT `{token}` + - Record format: `_wizamart-verify.domain.com` TXT `{token}` **After verification:** - Domain can be activated diff --git a/app/core/config.py b/app/core/config.py index df3cea86..570194e6 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -73,7 +73,7 @@ class Settings(BaseSettings): auto_provision_ssl: bool = False # Set to True if using automated SSL # DNS verification - dns_verification_prefix: str = "_letzshop-verify" + dns_verification_prefix: str = "_wizamart-verify" dns_verification_ttl: int = 3600 model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2 diff --git a/app/services/vendor_domain_service.py b/app/services/vendor_domain_service.py index 7716d0df..73fd9da3 100644 --- a/app/services/vendor_domain_service.py +++ b/app/services/vendor_domain_service.py @@ -286,7 +286,7 @@ class VendorDomainService: Verify domain ownership via DNS TXT record. The vendor must add a TXT record: - Name: _letzshop-verify.{domain} + Name: _wizamart-verify.{domain} Value: {verification_token} Args: @@ -313,7 +313,7 @@ class VendorDomainService: # Query DNS TXT records try: txt_records = dns.resolver.resolve( - f"_letzshop-verify.{domain.domain}", + f"_wizamart-verify.{domain.domain}", 'TXT' ) @@ -339,7 +339,7 @@ class VendorDomainService: except dns.resolver.NXDOMAIN: raise DomainVerificationFailedException( domain.domain, - f"DNS record _letzshop-verify.{domain.domain} not found" + f"DNS record _wizamart-verify.{domain.domain} not found" ) except dns.resolver.NoAnswer: raise DomainVerificationFailedException( @@ -394,7 +394,7 @@ class VendorDomainService: }, "txt_record": { "type": "TXT", - "name": "_letzshop-verify", + "name": "_wizamart-verify", "value": domain.verification_token, "ttl": 3600 }, diff --git a/app/templates/admin/base.html b/app/templates/admin/base.html index d26635f3..11ff6b00 100644 --- a/app/templates/admin/base.html +++ b/app/templates/admin/base.html @@ -40,7 +40,7 @@ - + diff --git a/app/templates/admin/vendor-theme.html b/app/templates/admin/vendor-theme.html index 7b598981..7b16c105 100644 --- a/app/templates/admin/vendor-theme.html +++ b/app/templates/admin/vendor-theme.html @@ -243,7 +243,7 @@

- + Typography

diff --git a/docs/__temp/10.stripe_payment_integration.md b/docs/__REVAMPING/10.stripe_payment_integration.md similarity index 100% rename from docs/__temp/10.stripe_payment_integration.md rename to docs/__REVAMPING/10.stripe_payment_integration.md diff --git a/docs/__temp/12.project_readme_final.md b/docs/__REVAMPING/12.project_readme_final.md similarity index 100% rename from docs/__temp/12.project_readme_final.md rename to docs/__REVAMPING/12.project_readme_final.md diff --git a/docs/__temp/13.updated_application_workflows_final.md b/docs/__REVAMPING/13.updated_application_workflows_final.md similarity index 100% rename from docs/__temp/13.updated_application_workflows_final.md rename to docs/__REVAMPING/13.updated_application_workflows_final.md diff --git a/docs/__temp/14.updated_complete_project_structure_final.md b/docs/__REVAMPING/14.updated_complete_project_structure_final.md similarity index 100% rename from docs/__temp/14.updated_complete_project_structure_final.md rename to docs/__REVAMPING/14.updated_complete_project_structure_final.md diff --git a/docs/__temp/6.complete_naming_convention.md b/docs/__REVAMPING/6.complete_naming_convention.md similarity index 100% rename from docs/__temp/6.complete_naming_convention.md rename to docs/__REVAMPING/6.complete_naming_convention.md diff --git a/docs/__temp/ARCHITECTURE_TEMP/STATS_SERVICE_ARCHITECTURE.md b/docs/__REVAMPING/ARCHITECTURE_TEMP/STATS_SERVICE_ARCHITECTURE.md similarity index 100% rename from docs/__temp/ARCHITECTURE_TEMP/STATS_SERVICE_ARCHITECTURE.md rename to docs/__REVAMPING/ARCHITECTURE_TEMP/STATS_SERVICE_ARCHITECTURE.md diff --git a/docs/__temp/BACKEND/ADMIN_FEATURE_INTEGRATION_GUIDE.md b/docs/__REVAMPING/BACKEND/ADMIN_FEATURE_INTEGRATION_GUIDE.md similarity index 100% rename from docs/__temp/BACKEND/ADMIN_FEATURE_INTEGRATION_GUIDE.md rename to docs/__REVAMPING/BACKEND/ADMIN_FEATURE_INTEGRATION_GUIDE.md diff --git a/docs/__temp/BACKEND/admin_integration_guide.md b/docs/__REVAMPING/BACKEND/admin_integration_guide.md similarity index 100% rename from docs/__temp/BACKEND/admin_integration_guide.md rename to docs/__REVAMPING/BACKEND/admin_integration_guide.md diff --git a/docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md b/docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md new file mode 100644 index 00000000..8e2b182d --- /dev/null +++ b/docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md @@ -0,0 +1,562 @@ +# Alpine.js Page Template - Quick Reference (WITH CENTRALIZED LOGGING) + +## ✅ Correct Page Structure + +```javascript +// static/admin/js/your-page.js (or vendor/shop) + +// 1. ✅ Use centralized logger (ONE LINE!) +const yourPageLog = window.LogConfig.loggers.yourPage; +// OR create custom if not pre-configured +// const yourPageLog = window.LogConfig.createLogger('YOUR-PAGE', window.LogConfig.logLevel); + +// 2. Create your Alpine.js component +function yourPageComponent() { + return { + // ✅ CRITICAL: Inherit base layout functionality + ...data(), + + // ✅ CRITICAL: Set page identifier + currentPage: 'your-page', + + // Your page-specific state + items: [], + loading: false, + error: null, + + // ✅ CRITICAL: Proper initialization with guard + async init() { + yourPageLog.info('=== YOUR PAGE INITIALIZING ==='); + + // Prevent multiple initializations + if (window._yourPageInitialized) { + yourPageLog.warn('Page already initialized, skipping...'); + return; + } + window._yourPageInitialized = true; + + // Load your data + await this.loadData(); + + yourPageLog.info('=== YOUR PAGE INITIALIZATION COMPLETE ==='); + }, + + // Your methods + async loadData() { + yourPageLog.info('Loading data...'); + this.loading = true; + this.error = null; + + try { + const startTime = performance.now(); + + // Log API request + const url = '/your/endpoint'; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + // ✅ CRITICAL: Use lowercase apiClient + const response = await apiClient.get(url); + + // Log API response + window.LogConfig.logApiCall('GET', url, response, 'response'); + + this.items = response.items || []; + + // Log performance + const duration = performance.now() - startTime; + window.LogConfig.logPerformance('Load Data', duration); + + yourPageLog.info(`Data loaded successfully`, { + count: this.items.length, + duration: `${duration}ms` + }); + + } catch (error) { + // Use centralized error logging + window.LogConfig.logError(error, 'Load Data'); + this.error = error.message; + Utils.showToast('Failed to load data', 'error'); + } finally { + this.loading = false; + } + }, + + // Format date helper (if needed) + formatDate(dateString) { + if (!dateString) return '-'; + return Utils.formatDate(dateString); + }, + + // Your other methods... + }; +} + +yourPageLog.info('Your page module loaded'); +``` + +--- + +## 🎯 Checklist for New Pages + +### HTML Template +```jinja2 +{# app/templates/admin/your-page.html #} +{% extends "admin/base.html" %} + +{% block title %}Your Page{% endblock %} + +{# ✅ CRITICAL: Link to your Alpine.js component #} +{% block alpine_data %}yourPageComponent(){% endblock %} + +{% block content %} + +{% endblock %} + +{% block extra_scripts %} +{# ✅ CRITICAL: Load your JavaScript file #} + +{% endblock %} +``` + +### JavaScript File Checklist + +- [ ] ✅ Use centralized logger (ONE line instead of 15!) +- [ ] ✅ Function name matches `alpine_data` in template +- [ ] ✅ `...data(),` at start of return object +- [ ] ✅ `currentPage: 'your-page'` set +- [ ] ✅ Initialization guard in `init()` +- [ ] ✅ Use lowercase `apiClient` for API calls +- [ ] ✅ Use `window.LogConfig.logApiCall()` for API logging +- [ ] ✅ Use `window.LogConfig.logPerformance()` for performance +- [ ] ✅ Use `window.LogConfig.logError()` for errors +- [ ] ✅ Module loaded log at end + +--- + +## 📦 Pre-configured Loggers by Frontend + +### Admin Frontend +```javascript +window.LogConfig.loggers.vendors // Vendor management +window.LogConfig.loggers.vendorTheme // Theme customization +window.LogConfig.loggers.vendorUsers // Vendor users +window.LogConfig.loggers.products // Product management +window.LogConfig.loggers.inventory // Inventory +window.LogConfig.loggers.orders // Order management +window.LogConfig.loggers.users // User management +window.LogConfig.loggers.audit // Audit logs +window.LogConfig.loggers.dashboard // Dashboard +window.LogConfig.loggers.imports // Import operations +``` + +### Vendor Frontend +```javascript +window.LogConfig.loggers.dashboard // Vendor dashboard +window.LogConfig.loggers.products // Product management +window.LogConfig.loggers.inventory // Inventory +window.LogConfig.loggers.orders // Order management +window.LogConfig.loggers.theme // Theme customization +window.LogConfig.loggers.settings // Settings +window.LogConfig.loggers.analytics // Analytics +``` + +### Shop Frontend +```javascript +window.LogConfig.loggers.catalog // Product browsing +window.LogConfig.loggers.product // Product details +window.LogConfig.loggers.search // Search +window.LogConfig.loggers.cart // Shopping cart +window.LogConfig.loggers.checkout // Checkout +window.LogConfig.loggers.account // User account +window.LogConfig.loggers.orders // Order history +window.LogConfig.loggers.wishlist // Wishlist +``` + +--- + +## ❌ Common Mistakes to Avoid + +### 1. Old Way vs New Way +```javascript +// ❌ OLD WAY - 15 lines of duplicate code +const YOUR_PAGE_LOG_LEVEL = 3; +const yourPageLog = { + error: (...args) => YOUR_PAGE_LOG_LEVEL >= 1 && console.error('❌ [YOUR_PAGE ERROR]', ...args), + warn: (...args) => YOUR_PAGE_LOG_LEVEL >= 2 && console.warn('⚠️ [YOUR_PAGE WARN]', ...args), + info: (...args) => YOUR_PAGE_LOG_LEVEL >= 3 && console.info('ℹ️ [YOUR_PAGE INFO]', ...args), + debug: (...args) => YOUR_PAGE_LOG_LEVEL >= 4 && console.log('🔍 [YOUR_PAGE DEBUG]', ...args) +}; + +// ✅ NEW WAY - 1 line! +const yourPageLog = window.LogConfig.loggers.yourPage; +``` + +### 2. Missing Base Inheritance +```javascript +// ❌ WRONG +function myPage() { + return { + items: [], + // Missing ...data() + }; +} + +// ✅ CORRECT +function myPage() { + return { + ...data(), // Must be first! + items: [], + }; +} +``` + +### 3. Wrong API Client Name +```javascript +// ❌ WRONG - Capital letters +await ApiClient.get('/endpoint'); +await API_CLIENT.get('/endpoint'); + +// ✅ CORRECT - lowercase +await apiClient.get('/endpoint'); +``` + +### 4. Missing Init Guard +```javascript +// ❌ WRONG +async init() { + await this.loadData(); +} + +// ✅ CORRECT +async init() { + if (window._myPageInitialized) return; + window._myPageInitialized = true; + await this.loadData(); +} +``` + +### 5. Missing currentPage +```javascript +// ❌ WRONG +return { + ...data(), + items: [], + // Missing currentPage +}; + +// ✅ CORRECT +return { + ...data(), + currentPage: 'my-page', // Must set this! + items: [], +}; +``` + +--- + +## 🔧 API Client Pattern + +### GET Request +```javascript +try { + const response = await apiClient.get('/endpoint'); + this.data = response; +} catch (error) { + console.error('Failed:', error); + Utils.showToast('Failed to load', 'error'); +} +``` + +### POST Request +```javascript +try { + const response = await apiClient.post('/endpoint', { + name: 'value', + // ... data + }); + Utils.showToast('Created successfully', 'success'); +} catch (error) { + console.error('Failed:', error); + Utils.showToast('Failed to create', 'error'); +} +``` + +### PUT Request +```javascript +try { + const response = await apiClient.put('/endpoint/123', { + name: 'updated value' + }); + Utils.showToast('Updated successfully', 'success'); +} catch (error) { + console.error('Failed:', error); + Utils.showToast('Failed to update', 'error'); +} +``` + +### DELETE Request +```javascript +try { + await apiClient.delete('/endpoint/123'); + Utils.showToast('Deleted successfully', 'success'); + await this.reloadData(); +} catch (error) { + console.error('Failed:', error); + Utils.showToast('Failed to delete', 'error'); +} +``` + +--- + +## 🔧 Centralized Logging Patterns + +### Basic Logging +```javascript +const log = window.LogConfig.loggers.yourPage; + +log.info('Page loaded'); +log.warn('Connection slow'); +log.error('Failed to load data', error); +log.debug('User data:', userData); +``` + +### Grouped Logging +```javascript +log.group('Loading Theme Data'); +log.info('Fetching vendor...'); +log.info('Fetching theme...'); +log.info('Fetching presets...'); +log.groupEnd(); +``` + +### API Call Logging +```javascript +const url = '/api/vendors'; + +// Before request +window.LogConfig.logApiCall('GET', url, null, 'request'); + +// Make request +const data = await apiClient.get(url); + +// After response +window.LogConfig.logApiCall('GET', url, data, 'response'); +``` + +### Error Logging +```javascript +try { + await saveTheme(); +} catch (error) { + window.LogConfig.logError(error, 'Save Theme'); +} +``` + +### Performance Logging +```javascript +const start = performance.now(); +await loadThemeData(); +const duration = performance.now() - start; +window.LogConfig.logPerformance('Load Theme Data', duration); +``` + +### Table Logging +```javascript +log.table([ + { id: 1, name: 'Vendor A', status: 'active' }, + { id: 2, name: 'Vendor B', status: 'inactive' } +]); +``` + +--- + +## 📚 Benefits of Centralized Logging + +| Aspect | Old Way | New Way | +|--------|---------|---------| +| **Lines of code** | ~15 per file | 1 line per file | +| **Consistency** | Varies by file | Unified across all frontends | +| **Maintenance** | Update each file | Update one shared file | +| **Features** | Basic logging | Advanced (groups, perf, API) | +| **Environment** | Manual config | Auto-detected | +| **Frontend aware** | No | Yes (admin/vendor/shop) | +| **Log levels** | Per file | Per frontend + environment | + +--- + +## 🎨 Common UI Patterns + +### Loading State +```javascript +async loadData() { + this.loading = true; + try { + const data = await apiClient.get('/endpoint'); + this.items = data; + } finally { + this.loading = false; + } +} +``` + +### Refresh/Reload +```javascript +async refresh() { + console.info('Refreshing...'); + await this.loadData(); + Utils.showToast('Refreshed successfully', 'success'); +} +``` + +--- + +## 📚 Available Utilities + +### From `init-alpine.js` (via `...data()`) +- `this.dark` - Dark mode state +- `this.toggleTheme()` - Toggle theme +- `this.isSideMenuOpen` - Side menu state +- `this.toggleSideMenu()` - Toggle side menu +- `this.closeSideMenu()` - Close side menu +- `this.isNotificationsMenuOpen` - Notifications menu state +- `this.toggleNotificationsMenu()` - Toggle notifications +- `this.closeNotificationsMenu()` - Close notifications +- `this.isProfileMenuOpen` - Profile menu state +- `this.toggleProfileMenu()` - Toggle profile menu +- `this.closeProfileMenu()` - Close profile menu +- `this.isPagesMenuOpen` - Pages menu state +- `this.togglePagesMenu()` - Toggle pages menu + +### From `Utils` (global) +- `Utils.showToast(message, type, duration)` - Show toast notification +- `Utils.formatDate(dateString)` - Format date for display +- `Utils.confirm(message, title)` - Show confirmation dialog (if available) + +### From `apiClient` (global) +- `apiClient.get(url)` - GET request +- `apiClient.post(url, data)` - POST request +- `apiClient.put(url, data)` - PUT request +- `apiClient.delete(url)` - DELETE request + +--- + +## 🎨 Complete Example + +```javascript +// static/admin/js/vendor-theme.js + +// 1. Use centralized logger +const themeLog = window.LogConfig.loggers.vendorTheme; + +// 2. Create component +function adminVendorTheme() { + return { + ...data(), + currentPage: 'vendor-theme', + + vendor: null, + themeData: {}, + loading: true, + + async init() { + themeLog.info('Initializing vendor theme editor'); + + if (window._vendorThemeInitialized) { + themeLog.warn('Already initialized, skipping...'); + return; + } + window._vendorThemeInitialized = true; + + const startTime = performance.now(); + + try { + themeLog.group('Loading theme data'); + + await Promise.all([ + this.loadVendor(), + this.loadTheme() + ]); + + themeLog.groupEnd(); + + const duration = performance.now() - startTime; + window.LogConfig.logPerformance('Theme Editor Init', duration); + + themeLog.info('Theme editor initialized successfully'); + + } catch (error) { + window.LogConfig.logError(error, 'Theme Editor Init'); + Utils.showToast('Failed to initialize', 'error'); + } finally { + this.loading = false; + } + }, + + async loadVendor() { + const url = `/admin/vendors/${this.vendorCode}`; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + const response = await apiClient.get(url); + this.vendor = response; + + window.LogConfig.logApiCall('GET', url, response, 'response'); + themeLog.debug('Vendor loaded:', this.vendor); + }, + + async saveTheme() { + themeLog.info('Saving theme changes'); + + try { + const url = `/admin/vendor-themes/${this.vendorCode}`; + window.LogConfig.logApiCall('PUT', url, this.themeData, 'request'); + + const response = await apiClient.put(url, this.themeData); + + window.LogConfig.logApiCall('PUT', url, response, 'response'); + + themeLog.info('Theme saved successfully'); + Utils.showToast('Theme saved', 'success'); + + } catch (error) { + window.LogConfig.logError(error, 'Save Theme'); + Utils.showToast('Failed to save theme', 'error'); + } + } + }; +} + +themeLog.info('Vendor theme editor module loaded'); +``` + +--- + +## 🚀 Quick Start Template Files + +Copy these to create a new page: + +1. **Copy base file:** `dashboard.js` → rename to `your-page.js` +2. **Update logger:** + ```javascript + // Change from: + const dashLog = window.LogConfig.loggers.dashboard; + // To: + const yourPageLog = window.LogConfig.loggers.yourPage; + // Or create custom: + const yourPageLog = window.LogConfig.createLogger('YOUR-PAGE'); + ``` +3. **Replace function name:** `adminDashboard()` → `yourPageComponent()` +4. **Update init flag:** `_dashboardInitialized` → `_yourPageInitialized` +5. **Update page identifier:** `currentPage: 'dashboard'` → `currentPage: 'your-page'` +6. **Replace data loading logic** with your API endpoints +7. **Update HTML template** to use your function name: + ```jinja2 + {% block alpine_data %}yourPageComponent(){% endblock %} + ``` +8. **Load your script** in the template: + ```jinja2 + {% block extra_scripts %} + + {% endblock %} + ``` + +Done! ✅ diff --git a/docs/__temp/FRONTEND/FRONTEND_ARCHITECTURE_OVERVIEW.txt b/docs/__REVAMPING/FRONTEND/FRONTEND_ARCHITECTURE_OVERVIEW.txt similarity index 100% rename from docs/__temp/FRONTEND/FRONTEND_ARCHITECTURE_OVERVIEW.txt rename to docs/__REVAMPING/FRONTEND/FRONTEND_ARCHITECTURE_OVERVIEW.txt diff --git a/docs/__REVAMPING/FRONTEND/FRONTEND_LOGGING_SYSTEM_QUICK_REFERENCE.md b/docs/__REVAMPING/FRONTEND/FRONTEND_LOGGING_SYSTEM_QUICK_REFERENCE.md new file mode 100644 index 00000000..0b650c6f --- /dev/null +++ b/docs/__REVAMPING/FRONTEND/FRONTEND_LOGGING_SYSTEM_QUICK_REFERENCE.md @@ -0,0 +1,410 @@ +# Quick Reference: Centralized Logging System + +## 🚀 Quick Start + +### In Any Page File (Admin/Vendor/Shop): + +```javascript +// 1. Use pre-configured logger (RECOMMENDED) +const pageLog = window.LogConfig.loggers.yourPage; + +// 2. Or create custom logger +const pageLog = window.LogConfig.createLogger('PAGE-NAME'); + +// 3. Use it +pageLog.info('Page loaded'); +pageLog.debug('Data:', data); +pageLog.warn('Warning message'); +pageLog.error('Error occurred', error); +``` + +--- + +## 📦 Pre-configured Loggers + +### Admin Frontend +```javascript +window.LogConfig.loggers.vendors +window.LogConfig.loggers.vendorTheme +window.LogConfig.loggers.products +window.LogConfig.loggers.orders +window.LogConfig.loggers.users +window.LogConfig.loggers.dashboard +window.LogConfig.loggers.imports +``` + +### Vendor Frontend +```javascript +window.LogConfig.loggers.dashboard +window.LogConfig.loggers.products +window.LogConfig.loggers.orders +window.LogConfig.loggers.theme +window.LogConfig.loggers.analytics +``` + +### Shop Frontend +```javascript +window.LogConfig.loggers.catalog +window.LogConfig.loggers.cart +window.LogConfig.loggers.checkout +window.LogConfig.loggers.product +``` + +--- + +## 🎨 Basic Logging + +```javascript +const log = window.LogConfig.loggers.myPage; + +// Simple messages +log.info('Operation started'); +log.warn('Slow connection detected'); +log.error('Operation failed'); +log.debug('Debug data:', { user: 'John', id: 123 }); + +// With multiple arguments +log.info('User logged in:', username, 'at', timestamp); +``` + +--- + +## 🔥 Advanced Features + +### API Call Logging +```javascript +const url = '/api/vendors'; + +// Before request +window.LogConfig.logApiCall('GET', url, null, 'request'); + +// Make request +const data = await apiClient.get(url); + +// After response +window.LogConfig.logApiCall('GET', url, data, 'response'); +``` + +### Performance Logging +```javascript +const start = performance.now(); +await expensiveOperation(); +const duration = performance.now() - start; + +window.LogConfig.logPerformance('Operation Name', duration); +// Output: ⚡ Operation Name took 45ms (fast) +// Output: ⏱️ Operation Name took 250ms (medium) +// Output: 🐌 Operation Name took 750ms (slow) +``` + +### Error Logging +```javascript +try { + await riskyOperation(); +} catch (error) { + window.LogConfig.logError(error, 'Operation Context'); + // Automatically logs error message and stack trace +} +``` + +### Grouped Logging +```javascript +log.group('Loading Data'); +log.info('Fetching users...'); +log.info('Fetching products...'); +log.info('Fetching orders...'); +log.groupEnd(); +``` + +### Table Logging +```javascript +const users = [ + { id: 1, name: 'John', status: 'active' }, + { id: 2, name: 'Jane', status: 'inactive' } +]; +log.table(users); +``` + +### Timer Logging +```javascript +log.time('Data Processing'); +// ... long operation ... +log.timeEnd('Data Processing'); +// Output: ⏱️ [ADMIN:PAGE TIME] Data Processing: 1234.56ms +``` + +--- + +## 📋 Complete Page Template + +```javascript +// static/admin/js/my-page.js + +// 1. Setup logger +const pageLog = window.LogConfig.loggers.myPage; + +// 2. Create component +function myPageComponent() { + return { + ...data(), + currentPage: 'my-page', + + items: [], + loading: false, + + async init() { + pageLog.info('Initializing page'); + + // Prevent double init + if (window._myPageInitialized) return; + window._myPageInitialized = true; + + const start = performance.now(); + + try { + pageLog.group('Loading Data'); + await this.loadData(); + pageLog.groupEnd(); + + const duration = performance.now() - start; + window.LogConfig.logPerformance('Page Init', duration); + + pageLog.info('Page initialized successfully'); + } catch (error) { + window.LogConfig.logError(error, 'Page Init'); + } + }, + + async loadData() { + const url = '/api/my-data'; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + const data = await apiClient.get(url); + + window.LogConfig.logApiCall('GET', url, data, 'response'); + this.items = data; + } + }; +} + +pageLog.info('Page module loaded'); +``` + +--- + +## 🎯 Log Levels + +```javascript +LOG_LEVELS = { + ERROR: 1, // Only errors + WARN: 2, // Errors + warnings + INFO: 3, // Errors + warnings + info (default) + DEBUG: 4 // Everything +} +``` + +**Auto-detected:** +- `localhost` → DEBUG (4) +- Production → Varies by frontend + +**Manual override:** +```javascript +const myLog = window.LogConfig.createLogger('MY-PAGE', 4); // Force DEBUG +``` + +--- + +## 🔍 Debugging Tips + +### Check Current Config +```javascript +console.log(window.LogConfig.frontend); // 'admin' | 'vendor' | 'shop' +console.log(window.LogConfig.environment); // 'development' | 'production' +console.log(window.LogConfig.logLevel); // 1 | 2 | 3 | 4 +``` + +### Test Logger +```javascript +const test = window.LogConfig.createLogger('TEST', 4); +test.info('This is a test'); +test.debug('Debug info:', { test: true }); +``` + +### Enable Debug Mode +```javascript +// In browser console +window.LogConfig.loggers.myPage = window.LogConfig.createLogger('MY-PAGE', 4); +// Now reload page to see debug logs +``` + +--- + +## ⚠️ Common Mistakes + +### ❌ WRONG +```javascript +// Old way (don't do this!) +const LOG_LEVEL = 3; +const log = { + info: (...args) => LOG_LEVEL >= 3 && console.log(...args) +}; + +// Wrong case +ApiClient.get(url) // Should be apiClient +Logger.info() // Should be window.LogConfig +``` + +### ✅ CORRECT +```javascript +// New way +const log = window.LogConfig.loggers.myPage; + +// Correct case +apiClient.get(url) +window.LogConfig.loggers.myPage.info() +``` + +--- + +## 🎨 Console Output Examples + +### Development Mode +``` +🎛️ Admin Frontend Logging System Initialized + Environment: Development + Log Level: 4 (DEBUG) +ℹ️ [ADMIN:VENDORS INFO] Vendors module loaded +ℹ️ [ADMIN:VENDORS INFO] Initializing vendors page +📤 [ADMIN:API DEBUG] GET /admin/vendors +📥 [ADMIN:API DEBUG] GET /admin/vendors {data...} +⚡ [ADMIN:PERF DEBUG] Load Vendors took 45ms +ℹ️ [ADMIN:VENDORS INFO] Vendors loaded: 25 items +``` + +### Production Mode (Admin) +``` +🎛️ Admin Frontend Logging System Initialized + Environment: Production + Log Level: 2 (WARN) +⚠️ [ADMIN:API WARN] Slow API response: 2.5s +❌ [ADMIN:VENDORS ERROR] Failed to load vendor +``` + +--- + +## 📱 Frontend Detection + +The system automatically detects which frontend based on URL: + +| URL Path | Frontend | Prefix | +|----------|----------|--------| +| `/admin/*` | admin | `ADMIN:` | +| `/vendor/*` | vendor | `VENDOR:` | +| `/shop/*` | shop | `SHOP:` | + +--- + +## 🛠️ Customization + +### Add New Pre-configured Logger + +Edit `log-config.js`: + +```javascript +const adminLoggers = { + // ... existing loggers + myNewPage: createLogger('MY-NEW-PAGE', ACTIVE_LOG_LEVEL) +}; +``` + +### Change Log Level for Frontend + +Edit `log-config.js`: + +```javascript +const DEFAULT_LOG_LEVELS = { + admin: { + development: LOG_LEVELS.DEBUG, + production: LOG_LEVELS.INFO // Change this + } +}; +``` + +--- + +## 📚 Full API Reference + +```javascript +window.LogConfig = { + // Log levels + LOG_LEVELS: { ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4 }, + + // Current config + frontend: 'admin' | 'vendor' | 'shop', + environment: 'development' | 'production', + logLevel: 1 | 2 | 3 | 4, + + // Default logger + log: { error(), warn(), info(), debug(), group(), groupEnd(), table(), time(), timeEnd() }, + + // Pre-configured loggers + loggers: { vendors, products, orders, ... }, + + // Create custom logger + createLogger(prefix, level?), + + // Utility functions + logApiCall(method, url, data?, status), + logError(error, context), + logPerformance(operation, duration) +} +``` + +--- + +## 💡 Tips & Tricks + +1. **Use Pre-configured Loggers** + - Faster to write + - Consistent naming + - Already configured + +2. **Log API Calls** + - Easy to track requests + - See request/response data + - Debug API issues + +3. **Track Performance** + - Find slow operations + - Optimize bottlenecks + - Monitor page load times + +4. **Group Related Logs** + - Cleaner console + - Easier to follow + - Better debugging + +5. **Use Debug Level in Development** + - See everything + - Find issues faster + - Learn how code flows + +--- + +## 📖 More Documentation + +- [Migration Guide](MIGRATION_GUIDE.md) +- [Alpine.js Template V2](ALPINE_PAGE_TEMPLATE_V2.md) +- [Recommendation Summary](RECOMMENDATION_SUMMARY.md) + +--- + +**Print this page and keep it handy!** 📌 + +Quick pattern to remember: +```javascript +const log = window.LogConfig.loggers.myPage; +log.info('Hello from my page!'); +``` + +That's it! 🎉 diff --git a/docs/__temp/FRONTEND/FRONTEND_SIDEBAR-COMPLETE_IMPLEMENTATION_GUIDE.md b/docs/__REVAMPING/FRONTEND/FRONTEND_SIDEBAR-COMPLETE_IMPLEMENTATION_GUIDE.md similarity index 100% rename from docs/__temp/FRONTEND/FRONTEND_SIDEBAR-COMPLETE_IMPLEMENTATION_GUIDE.md rename to docs/__REVAMPING/FRONTEND/FRONTEND_SIDEBAR-COMPLETE_IMPLEMENTATION_GUIDE.md diff --git a/docs/__temp/FRONTEND/FRONTEND_UI_COMPONENTS.md b/docs/__REVAMPING/FRONTEND/FRONTEND_UI_COMPONENTS.md similarity index 100% rename from docs/__temp/FRONTEND/FRONTEND_UI_COMPONENTS.md rename to docs/__REVAMPING/FRONTEND/FRONTEND_UI_COMPONENTS.md diff --git a/docs/__temp/FRONTEND/FRONTEND_UI_COMPONENTS_QUICK_REFERENCE.md b/docs/__REVAMPING/FRONTEND/FRONTEND_UI_COMPONENTS_QUICK_REFERENCE.md similarity index 100% rename from docs/__temp/FRONTEND/FRONTEND_UI_COMPONENTS_QUICK_REFERENCE.md rename to docs/__REVAMPING/FRONTEND/FRONTEND_UI_COMPONENTS_QUICK_REFERENCE.md diff --git a/docs/__temp/FRONTEND/PAGINATION_DOCUMENTATION.md b/docs/__REVAMPING/FRONTEND/PAGINATION_DOCUMENTATION.md similarity index 100% rename from docs/__temp/FRONTEND/PAGINATION_DOCUMENTATION.md rename to docs/__REVAMPING/FRONTEND/PAGINATION_DOCUMENTATION.md diff --git a/docs/__temp/FRONTEND/PAGINATION_QUICK_START.txt b/docs/__REVAMPING/FRONTEND/PAGINATION_QUICK_START.txt similarity index 100% rename from docs/__temp/FRONTEND/PAGINATION_QUICK_START.txt rename to docs/__REVAMPING/FRONTEND/PAGINATION_QUICK_START.txt diff --git a/docs/__temp/FRONTEND/frontend-structure.txt b/docs/__REVAMPING/FRONTEND/frontend-structure.txt similarity index 100% rename from docs/__temp/FRONTEND/frontend-structure.txt rename to docs/__REVAMPING/FRONTEND/frontend-structure.txt diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_ARCHITECTURE_DIAGRAMS.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_ARCHITECTURE_DIAGRAMS.md similarity index 99% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_ARCHITECTURE_DIAGRAMS.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_ARCHITECTURE_DIAGRAMS.md index d5a12ac4..4478ec1c 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_ARCHITECTURE_DIAGRAMS.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_ARCHITECTURE_DIAGRAMS.md @@ -402,7 +402,7 @@ Value: 123.45.67.89 TTL: 3600 Type: TXT -Name: _letzshop-verify +Name: _wizamart-verify Value: abc123xyz (verification token from your platform) TTL: 3600 ``` diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md similarity index 99% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md index af37895e..0f3023e9 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_CUSTOM_DOMAIN_IMPLEMENTATION_GUIDE.md @@ -161,7 +161,7 @@ To prevent domain hijacking, verify the vendor owns the domain: 2. System generates verification token: `abc123xyz` 3. Vendor adds DNS TXT record: ``` - Name: _letzshop-verify.customdomain1.com + Name: _wizamart-verify.customdomain1.com Type: TXT Value: abc123xyz ``` @@ -177,7 +177,7 @@ def verify_domain(domain_id: int, db: Session): # Query DNS for TXT record txt_records = dns.resolver.resolve( - f"_letzshop-verify.{domain.domain}", + f"_wizamart-verify.{domain.domain}", 'TXT' ) diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_EXECUTIVE_SUMMARY.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_EXECUTIVE_SUMMARY.md similarity index 99% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_EXECUTIVE_SUMMARY.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_EXECUTIVE_SUMMARY.md index c9e51ccd..4783c609 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_EXECUTIVE_SUMMARY.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_EXECUTIVE_SUMMARY.md @@ -116,7 +116,7 @@ Name: @ Value: 123.45.67.89 (your server IP) Type: TXT -Name: _letzshop-verify +Name: _wizamart-verify Value: abc123xyz ``` diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_IMPLEMENTATION_CHECKLIST.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_IMPLEMENTATION_CHECKLIST.md similarity index 98% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_IMPLEMENTATION_CHECKLIST.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_IMPLEMENTATION_CHECKLIST.md index e41a4ea7..532b3fbb 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_IMPLEMENTATION_CHECKLIST.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_IMPLEMENTATION_CHECKLIST.md @@ -125,7 +125,7 @@ pip install dnspython import dns.resolver # Test querying TXT record -answers = dns.resolver.resolve("_letzshop-verify.example.com", "TXT") +answers = dns.resolver.resolve("_wizamart-verify.example.com", "TXT") for txt in answers: print(txt.to_text()) ``` @@ -259,7 +259,7 @@ TTL: 3600 **Verification TXT Record:** ``` Type: TXT -Name: _letzshop-verify +Name: _wizamart-verify Value: [token from step 9.1] TTL: 3600 ``` @@ -351,7 +351,7 @@ HAVING COUNT(*) > 1; **Check DNS propagation:** ```bash # Check if TXT record exists -dig _letzshop-verify.customdomain1.com TXT +dig _wizamart-verify.customdomain1.com TXT # Should show verification token ``` diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_INDEX.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_INDEX.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_INDEX.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_INDEX.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_QUICK_START.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_QUICK_START.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_QUICK_START.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_QUICK_START.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_README.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_README.md similarity index 99% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_README.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_README.md index 9f683968..c2d19b09 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_README.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITENANT_ARCHITECTURE/MULTITENANT_README.md @@ -203,7 +203,7 @@ When a vendor wants to use `customdomain1.com`: Value: 123.45.67.89 (your server IP) 2. Verification TXT Record: - Name: _letzshop-verify + Name: _wizamart-verify Value: [token from your platform] 3. Wait 5-15 minutes for DNS propagation diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/MULTI_THEME_SHOP_GUIDE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/MULTI_THEME_SHOP_GUIDE.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/MULTI_THEME_SHOP_GUIDE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/MULTI_THEME_SHOP_GUIDE.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_ARCHITECTURE_COMPLIANT_GUIDE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_ARCHITECTURE_COMPLIANT_GUIDE.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_ARCHITECTURE_COMPLIANT_GUIDE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_ARCHITECTURE_COMPLIANT_GUIDE.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_INTEGRATION_YOUR_ARCHITECTURE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_INTEGRATION_YOUR_ARCHITECTURE.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_INTEGRATION_YOUR_ARCHITECTURE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_INTEGRATION_YOUR_ARCHITECTURE.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PRESETS_GUIDE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PRESETS_GUIDE.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PRESETS_GUIDE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PRESETS_GUIDE.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PROPER_ARCHITECTURE_GUIDE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PROPER_ARCHITECTURE_GUIDE.md similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PROPER_ARCHITECTURE_GUIDE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/THEME_PROPER_ARCHITECTURE_GUIDE.md diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_base_template.html b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_base_template.html similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_base_template.html rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_base_template.html diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_layout.js b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_layout.js similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_layout.js rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/shop_layout.js diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/theme_context_middleware.py b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/theme_context_middleware.py similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/theme_context_middleware.py rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/theme_context_middleware.py diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/vendor_theme_model.py b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/vendor_theme_model.py similarity index 100% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/vendor_theme_model.py rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/MULTITHEMES_SHOP_GUIDE/vendor_theme_model.py diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_ARCHITECTURE_DIAGRAMS.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_ARCHITECTURE_DIAGRAMS.md similarity index 99% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_ARCHITECTURE_DIAGRAMS.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_ARCHITECTURE_DIAGRAMS.md index 656ae717..6d17e5da 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_ARCHITECTURE_DIAGRAMS.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_ARCHITECTURE_DIAGRAMS.md @@ -331,7 +331,7 @@ Step 2: Get Instructions ┌────────────────────────────────────┐ │ System returns instructions: │ │ "Add TXT record: │ -│ _letzshop-verify.myshop.com │ +│ _wizamart-verify.myshop.com │ │ Value: abc123..." │ └────────────────────────────────────┘ @@ -343,7 +343,7 @@ Step 3: Vendor Adds DNS Record ▼ ┌────────────────────────────────────┐ │ DNS Provider (GoDaddy/etc) │ -│ _letzshop-verify.myshop.com TXT │ +│ _wizamart-verify.myshop.com TXT │ │ "abc123..." │ └────────────────────────────────────┘ diff --git a/docs/__temp/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_IMPLEMENTATION_GUIDE.md b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_IMPLEMENTATION_GUIDE.md similarity index 99% rename from docs/__temp/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_IMPLEMENTATION_GUIDE.md rename to docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_IMPLEMENTATION_GUIDE.md index 5ad19b5f..604d94e5 100644 --- a/docs/__temp/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_IMPLEMENTATION_GUIDE.md +++ b/docs/__REVAMPING/FRONT_TO_BACK_MULTITENANT/VENDOR_DOMAIN/VENDOR_DOMAIN_IMPLEMENTATION_GUIDE.md @@ -356,14 +356,14 @@ GET /api/v1/admin/vendors/domains/1/verification-instructions }, "txt_record": { "type": "TXT", - "name": "_letzshop-verify", + "name": "_wizamart-verify", "value": "abc123xyz...", "ttl": 3600 } } # Step 2: Vendor adds DNS record -# _letzshop-verify.myshop.com TXT "abc123xyz..." +# _wizamart-verify.myshop.com TXT "abc123xyz..." # Step 3: Verify domain POST /api/v1/admin/vendors/domains/1/verify diff --git a/docs/__temp/__PROJECT_ROADMAP/19_migration_plan_FINAL.md b/docs/__REVAMPING/__PROJECT_ROADMAP/19_migration_plan_FINAL.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/19_migration_plan_FINAL.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/19_migration_plan_FINAL.md diff --git a/docs/__temp/__PROJECT_ROADMAP/FRONTEND_DOCUMENTATION_PLAN.md b/docs/__REVAMPING/__PROJECT_ROADMAP/FRONTEND_DOCUMENTATION_PLAN.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/FRONTEND_DOCUMENTATION_PLAN.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/FRONTEND_DOCUMENTATION_PLAN.md diff --git a/docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/15.web-architecture-revamping.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/15.web-architecture-revamping.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/15.web-architecture-revamping.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/15.web-architecture-revamping.md diff --git a/docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-2.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-2.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-2.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-2.md diff --git a/docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-3.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-3.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-3.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress-3.md diff --git a/docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress.md b/docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/JINJA_MIGRATION/16.jinja2_migration_progress.md diff --git a/docs/__temp/__PROJECT_ROADMAP/ROUTE_MIGRATION_SUMMARY.txt b/docs/__REVAMPING/__PROJECT_ROADMAP/ROUTE_MIGRATION_SUMMARY.txt similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/ROUTE_MIGRATION_SUMMARY.txt rename to docs/__REVAMPING/__PROJECT_ROADMAP/ROUTE_MIGRATION_SUMMARY.txt diff --git a/docs/__temp/__PROJECT_ROADMAP/implementation_roadmap.md b/docs/__REVAMPING/__PROJECT_ROADMAP/implementation_roadmap.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/implementation_roadmap.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/implementation_roadmap.md diff --git a/docs/__temp/__PROJECT_ROADMAP/slice1_doc.md b/docs/__REVAMPING/__PROJECT_ROADMAP/slice1_doc.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/slice1_doc.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/slice1_doc.md diff --git a/docs/__temp/__PROJECT_ROADMAP/slice2_doc.md b/docs/__REVAMPING/__PROJECT_ROADMAP/slice2_doc.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/slice2_doc.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/slice2_doc.md diff --git a/docs/__temp/__PROJECT_ROADMAP/slice3_doc.md b/docs/__REVAMPING/__PROJECT_ROADMAP/slice3_doc.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/slice3_doc.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/slice3_doc.md diff --git a/docs/__temp/__PROJECT_ROADMAP/slice4_doc.md b/docs/__REVAMPING/__PROJECT_ROADMAP/slice4_doc.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/slice4_doc.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/slice4_doc.md diff --git a/docs/__temp/__PROJECT_ROADMAP/slice5_doc.md b/docs/__REVAMPING/__PROJECT_ROADMAP/slice5_doc.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/slice5_doc.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/slice5_doc.md diff --git a/docs/__temp/__PROJECT_ROADMAP/slice_overview.md b/docs/__REVAMPING/__PROJECT_ROADMAP/slice_overview.md similarity index 100% rename from docs/__temp/__PROJECT_ROADMAP/slice_overview.md rename to docs/__REVAMPING/__PROJECT_ROADMAP/slice_overview.md diff --git a/docs/__temp/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md b/docs/__temp/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md deleted file mode 100644 index 245a1340..00000000 --- a/docs/__temp/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md +++ /dev/null @@ -1,331 +0,0 @@ -# Alpine.js Page Template - Quick Reference - -## ✅ Correct Page Structure - -```javascript -// static/admin/js/your-page.js - -// 1. Setup logging (optional but recommended) -const YOUR_PAGE_LOG_LEVEL = 3; - -const yourPageLog = { - error: (...args) => YOUR_PAGE_LOG_LEVEL >= 1 && console.error('❌ [YOUR_PAGE ERROR]', ...args), - warn: (...args) => YOUR_PAGE_LOG_LEVEL >= 2 && console.warn('⚠️ [YOUR_PAGE WARN]', ...args), - info: (...args) => YOUR_PAGE_LOG_LEVEL >= 3 && console.info('ℹ️ [YOUR_PAGE INFO]', ...args), - debug: (...args) => YOUR_PAGE_LOG_LEVEL >= 4 && console.log('🔍 [YOUR_PAGE DEBUG]', ...args) -}; - -// 2. Create your Alpine.js component -function yourPageComponent() { - return { - // ✅ CRITICAL: Inherit base layout functionality - ...data(), - - // ✅ CRITICAL: Set page identifier - currentPage: 'your-page', - - // Your page-specific state - items: [], - loading: false, - error: null, - - // ✅ CRITICAL: Proper initialization with guard - async init() { - yourPageLog.info('=== YOUR PAGE INITIALIZING ==='); - - // Prevent multiple initializations - if (window._yourPageInitialized) { - yourPageLog.warn('Page already initialized, skipping...'); - return; - } - window._yourPageInitialized = true; - - // Load your data - await this.loadData(); - - yourPageLog.info('=== YOUR PAGE INITIALIZATION COMPLETE ==='); - }, - - // Your methods - async loadData() { - yourPageLog.info('Loading data...'); - this.loading = true; - this.error = null; - - try { - const startTime = Date.now(); - // ✅ CRITICAL: Use lowercase apiClient - const response = await apiClient.get('/your/endpoint'); - const duration = Date.now() - startTime; - - this.items = response.items || []; - - yourPageLog.info(`Data loaded in ${duration}ms`, { - count: this.items.length - }); - - } catch (error) { - yourPageLog.error('Failed to load data:', error); - this.error = error.message; - Utils.showToast('Failed to load data', 'error'); - } finally { - this.loading = false; - } - }, - - // Format date helper (if needed) - formatDate(dateString) { - if (!dateString) return '-'; - return Utils.formatDate(dateString); - }, - - // Your other methods... - }; -} - -yourPageLog.info('Your page module loaded'); -``` - ---- - -## 🎯 Checklist for New Pages - -### HTML Template -```jinja2 -{# app/templates/admin/your-page.html #} -{% extends "admin/base.html" %} - -{% block title %}Your Page{% endblock %} - -{# ✅ CRITICAL: Link to your Alpine.js component #} -{% block alpine_data %}yourPageComponent(){% endblock %} - -{% block content %} - -{% endblock %} - -{% block extra_scripts %} -{# ✅ CRITICAL: Load your JavaScript file #} - -{% endblock %} -``` - -### JavaScript File Checklist - -- [ ] ✅ Logging setup (optional) -- [ ] ✅ Function name matches `alpine_data` in template -- [ ] ✅ `...data(),` at start of return object -- [ ] ✅ `currentPage: 'your-page'` set -- [ ] ✅ Initialization guard in `init()` -- [ ] ✅ Use lowercase `apiClient` for API calls -- [ ] ✅ Use your custom logger (not `Logger`) -- [ ] ✅ Performance tracking with `Date.now()` (optional) -- [ ] ✅ Module loaded log at end - ---- - -## ❌ Common Mistakes to Avoid - -### 1. Missing Base Inheritance -```javascript -// ❌ WRONG -function myPage() { - return { - items: [], - // Missing ...data() - }; -} - -// ✅ CORRECT -function myPage() { - return { - ...data(), // Must be first! - items: [], - }; -} -``` - -### 2. Wrong API Client Name -```javascript -// ❌ WRONG - Capital letters -await ApiClient.get('/endpoint'); -await API_CLIENT.get('/endpoint'); - -// ✅ CORRECT - lowercase -await apiClient.get('/endpoint'); -``` - -### 3. Missing Init Guard -```javascript -// ❌ WRONG -async init() { - await this.loadData(); -} - -// ✅ CORRECT -async init() { - if (window._myPageInitialized) return; - window._myPageInitialized = true; - await this.loadData(); -} -``` - -### 4. Missing currentPage -```javascript -// ❌ WRONG -return { - ...data(), - items: [], - // Missing currentPage -}; - -// ✅ CORRECT -return { - ...data(), - currentPage: 'my-page', // Must set this! - items: [], -}; -``` - ---- - -## 🔧 API Client Pattern - -### GET Request -```javascript -try { - const response = await apiClient.get('/endpoint'); - this.data = response; -} catch (error) { - console.error('Failed:', error); - Utils.showToast('Failed to load', 'error'); -} -``` - -### POST Request -```javascript -try { - const response = await apiClient.post('/endpoint', { - name: 'value', - // ... data - }); - Utils.showToast('Created successfully', 'success'); -} catch (error) { - console.error('Failed:', error); - Utils.showToast('Failed to create', 'error'); -} -``` - -### PUT Request -```javascript -try { - const response = await apiClient.put('/endpoint/123', { - name: 'updated value' - }); - Utils.showToast('Updated successfully', 'success'); -} catch (error) { - console.error('Failed:', error); - Utils.showToast('Failed to update', 'error'); -} -``` - -### DELETE Request -```javascript -try { - await apiClient.delete('/endpoint/123'); - Utils.showToast('Deleted successfully', 'success'); - await this.reloadData(); -} catch (error) { - console.error('Failed:', error); - Utils.showToast('Failed to delete', 'error'); -} -``` - ---- - -## 🎨 Common UI Patterns - -### Loading State -```javascript -async loadData() { - this.loading = true; - try { - const data = await apiClient.get('/endpoint'); - this.items = data; - } finally { - this.loading = false; - } -} -``` - -### Error Handling -```javascript -async loadData() { - this.loading = true; - this.error = null; - try { - const data = await apiClient.get('/endpoint'); - this.items = data; - } catch (error) { - this.error = error.message; - Utils.showToast('Failed to load', 'error'); - } finally { - this.loading = false; - } -} -``` - -### Refresh/Reload -```javascript -async refresh() { - console.info('Refreshing...'); - await this.loadData(); - Utils.showToast('Refreshed successfully', 'success'); -} -``` - ---- - -## 📚 Available Utilities - -### From `init-alpine.js` (via `...data()`) -- `this.dark` - Dark mode state -- `this.toggleTheme()` - Toggle theme -- `this.isSideMenuOpen` - Side menu state -- `this.toggleSideMenu()` - Toggle side menu -- `this.closeSideMenu()` - Close side menu -- `this.isNotificationsMenuOpen` - Notifications menu state -- `this.toggleNotificationsMenu()` - Toggle notifications -- `this.closeNotificationsMenu()` - Close notifications -- `this.isProfileMenuOpen` - Profile menu state -- `this.toggleProfileMenu()` - Toggle profile menu -- `this.closeProfileMenu()` - Close profile menu -- `this.isPagesMenuOpen` - Pages menu state -- `this.togglePagesMenu()` - Toggle pages menu - -### From `Utils` (global) -- `Utils.showToast(message, type, duration)` - Show toast notification -- `Utils.formatDate(dateString)` - Format date for display -- `Utils.confirm(message, title)` - Show confirmation dialog (if available) - -### From `apiClient` (global) -- `apiClient.get(url)` - GET request -- `apiClient.post(url, data)` - POST request -- `apiClient.put(url, data)` - PUT request -- `apiClient.delete(url)` - DELETE request - ---- - -## 🚀 Quick Start Template Files - -Copy these to create a new page: - -1. Copy `dashboard.js` → rename to `your-page.js` -2. Replace function name: `adminDashboard()` → `yourPageComponent()` -3. Update logging prefix: `dashLog` → `yourPageLog` -4. Update init flag: `_dashboardInitialized` → `_yourPageInitialized` -5. Update `currentPage`: `'dashboard'` → `'your-page'` -6. Replace data loading logic with your endpoints -7. Update HTML template to use your function name - -Done! ✅ diff --git a/docs/getting-started/DATABASE_QUICK_REFERENCE.md b/docs/getting-started/DATABASE_QUICK_REFERENCE.md new file mode 100644 index 00000000..5776358b --- /dev/null +++ b/docs/getting-started/DATABASE_QUICK_REFERENCE.md @@ -0,0 +1,235 @@ +# Database Quick Reference Card + +Quick reference for common database operations. See `DATABASE_SETUP_GUIDE.md` for detailed instructions. + +--- + +## 🚀 Quick Start (New Developer) + +```bash +# 1. Clone and setup +git clone +cd letzshop +python -m venv venv +venv\Scripts\activate # Windows +pip install -r requirements.txt + +# 2. Initialize database +alembic upgrade head +python scripts/seed_database.py + +# 3. Start server +python -m uvicorn app.main:app --reload + +# 4. Login +# http://localhost:8000/admin/login +# Username: admin | Password: admin123 +``` + +--- + +## 🔄 Reset Database (Clean Slate) + +**Windows:** +```powershell +.\scripts\reset_database.ps1 +``` + +**Linux/Mac:** +```bash +./scripts/reset_database.sh +``` + +**Manual:** +```bash +# 1. Delete migrations +Remove-Item alembic\versions\*.py -Exclude __init__.py + +# 2. Delete database +Remove-Item wizamart.db + +# 3. Regenerate +alembic revision --autogenerate -m "Initial migration" +alembic upgrade head +python scripts/seed_database.py +``` + +--- + +## 📝 Creating Migrations + +```bash +# After modifying models in models/database/ + +# 1. Generate migration +alembic revision --autogenerate -m "Add field to vendor_themes" + +# 2. Review the file +# Check: alembic/versions/_add_field_to_vendor_themes.py + +# 3. Apply migration +alembic upgrade head +``` + +--- + +## 🔍 Checking Status + +```bash +# Current migration version +alembic current + +# Migration history +alembic history --verbose + +# List all tables +python -c "import sqlite3; conn = sqlite3.connect('wizamart.db'); cursor = conn.cursor(); cursor.execute('SELECT name FROM sqlite_master WHERE type=\"table\" ORDER BY name;'); [print(t[0]) for t in cursor.fetchall()]" + +# Check specific table +python -c "import sqlite3; conn = sqlite3.connect('wizamart.db'); cursor = conn.cursor(); cursor.execute('PRAGMA table_info(vendor_themes)'); [print(col) for col in cursor.fetchall()]" +``` + +--- + +## ⬆️ Upgrading Database + +```bash +# Upgrade to latest +alembic upgrade head + +# Upgrade one version +alembic upgrade +1 + +# Upgrade to specific revision +alembic upgrade +``` + +--- + +## ⬇️ Rolling Back + +```bash +# Downgrade one version +alembic downgrade -1 + +# Downgrade to specific revision +alembic downgrade + +# Downgrade to empty database +alembic downgrade base +``` + +--- + +## 🌱 Seeding Data + +```bash +# Seed development data +python scripts/seed_database.py + +# Manual seed (Python shell) +python +>>> from scripts.seed_database import seed_database +>>> seed_database() +``` + +--- + +## 🐛 Common Fixes + +### "No such table" Error +```bash +# Check model imported in alembic/env.py +# Then regenerate: +alembic revision --autogenerate -m "Add missing table" +alembic upgrade head +``` + +### "Database locked" +```bash +# Stop server +taskkill /F /IM python.exe # Windows +pkill python # Linux/Mac + +# Try again +alembic upgrade head +``` + +### "Target database not up to date" +```bash +alembic stamp head +alembic upgrade head +``` + +### "Multiple head revisions" +```bash +alembic merge heads -m "Merge migrations" +alembic upgrade head +``` + +--- + +## 📦 Adding New Models + +**Checklist:** + +```bash +# 1. Create model file +# models/database/my_model.py + +# 2. Import in alembic/env.py +# from models.database.my_model import MyModel + +# 3. Generate migration +alembic revision --autogenerate -m "Add MyModel table" + +# 4. Review migration file +# alembic/versions/_add_mymodel_table.py + +# 5. Apply migration +alembic upgrade head + +# 6. Test +python -c "from models.database.my_model import MyModel; print('✓ Import works')" +``` + +--- + +## 🔗 Important Files + +``` +├── alembic/ +│ ├── env.py ← Import ALL models here! +│ └── versions/ ← Migration files +├── app/core/ +│ └── database.py ← Database connection +├── models/database/ ← SQLAlchemy models +├── scripts/ +│ ├── seed_database.py ← Seed script +│ └── reset_database.ps1 ← Reset script +├── alembic.ini ← Alembic config +└── wizamart.db ← SQLite database +``` + +--- + +## 🆘 Getting Help + +1. Check full guide: `docs/DATABASE_SETUP_GUIDE.md` +2. Check Alembic docs: https://alembic.sqlalchemy.org/ +3. Ask team lead + +--- + +## ⚠️ Important Notes + +- ✅ Always review migrations before applying +- ✅ Test on local database first +- ✅ Keep migrations in version control +- ✅ Backup before destructive operations +- ❌ Never edit applied migrations +- ❌ Never delete migration files + +--- + +**Last Updated:** 2025-10-27 diff --git a/docs/getting-started/DATABASE_SETUP_GUIDE.md b/docs/getting-started/DATABASE_SETUP_GUIDE.md new file mode 100644 index 00000000..4456e4e3 --- /dev/null +++ b/docs/getting-started/DATABASE_SETUP_GUIDE.md @@ -0,0 +1,883 @@ +# Database Setup & Initialization Guide + +## Overview + +This guide walks you through setting up the LetzShop database from scratch. Whether you're a new developer joining the team or need to reset your local database, this document covers everything you need to know. + +--- + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [First-Time Setup](#first-time-setup) +3. [Database Reset (Clean Slate)](#database-reset-clean-slate) +4. [Verifying Your Setup](#verifying-your-setup) +5. [Common Issues & Solutions](#common-issues--solutions) +6. [Database Migrations](#database-migrations) +7. [Seeding Data](#seeding-data) + +--- + +## Prerequisites + +### Required Software + +- **Python 3.11+** +- **Alembic** (installed via `requirements.txt`) +- **SQLite** (built into Python) or **PostgreSQL** (for production-like setup) + +### Environment Setup + +```bash +# 1. Clone the repository +git clone +cd letzshop + +# 2. Create virtual environment +python -m venv venv + +# 3. Activate virtual environment +# Windows: +venv\Scripts\activate +# Linux/Mac: +source venv/bin/activate + +# 4. Install dependencies +pip install -r requirements.txt +``` + +--- + +## First-Time Setup + +### Step 1: Configure Database Connection + +Create or update `.env` file in project root: + +```env +# Database Configuration +DATABASE_URL=sqlite:///./wizamart.db + +# For PostgreSQL (production): +# DATABASE_URL=postgresql://user:password@localhost:5432/letzshop + +# Other required settings +SECRET_KEY=your-secret-key-here-change-in-production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=30 +``` + +--- + +### Step 2: Initialize Alembic (First Time Only) + +**If `alembic/` folder doesn't exist:** + +```bash +# Initialize Alembic +alembic init alembic +``` + +**Configure `alembic.ini`:** + +```ini +# alembic.ini +# Find this line and update: +sqlalchemy.url = sqlite:///./wizamart.db + +# Or for PostgreSQL: +# sqlalchemy.url = postgresql://user:password@localhost:5432/letzshop +``` + +**Configure `alembic/env.py`:** + +```python +# alembic/env.py + +# Add this near the top (after imports) +import sys +from pathlib import Path + +# Add project root to Python path +sys.path.append(str(Path(__file__).parents[1])) + +# ============================================================================ +# CRITICAL: Import ALL database models here +# ============================================================================ +from models.database.user import User +from models.database.vendor import Vendor +from models.database.vendor_domain import VendorDomain +from models.database.vendor_theme import VendorTheme +from models.database.customer import Customer +from models.database.team import Team, TeamMember, TeamInvitation +from models.database.product import Product +from models.database.marketplace_product import MarketplaceProduct +from models.database.inventory import Inventory +from models.database.order import Order +from models.database.marketplace_import_job import MarketplaceImportJob + +# Import Base +from app.core.database import Base + +# Set target metadata +target_metadata = Base.metadata +``` + +--- + +### Step 3: Generate Initial Migration + +```bash +# Generate migration from your models +alembic revision --autogenerate -m "Initial migration - all tables" + +# You should see output listing all detected tables: +# INFO [alembic.autogenerate.compare] Detected added table 'users' +# INFO [alembic.autogenerate.compare] Detected added table 'vendors' +# INFO [alembic.autogenerate.compare] Detected added table 'vendor_themes' +# etc. +``` + +--- + +### Step 4: Apply Migration + +```bash +# Apply the migration to create all tables +alembic upgrade head + +# You should see: +# INFO [alembic.runtime.migration] Running upgrade -> abc123def456, Initial migration - all tables +``` + +--- + +### Step 5: Seed Initial Data + +Create an admin user and test vendor: + +```bash +python scripts/seed_database.py +``` + +**Or manually:** + +```python +# Run Python interactive shell +python + +# Then execute: +from app.core.database import SessionLocal +from models.database.user import User +from models.database.vendor import Vendor +from middleware.auth import AuthManager +from datetime import datetime, timezone + +db = SessionLocal() +auth_manager = AuthManager() + +# Create admin user +admin = User( + username="admin", + email="admin@letzshop.com", + hashed_password=auth_manager.hash_password("admin123"), + role="admin", + is_active=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) +) +db.add(admin) +db.flush() + +# Create test vendor +vendor = Vendor( + vendor_code="TESTVENDOR", + subdomain="testvendor", + name="Test Vendor", + description="Development test vendor", + owner_user_id=admin.id, + contact_email="contact@testvendor.com", + is_active=True, + is_verified=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) +) +db.add(vendor) + +db.commit() +print("✅ Admin user and test vendor created!") +db.close() +exit() +``` + +--- + +### Step 6: Start the Application + +```bash +# Start FastAPI server +python -m uvicorn app.main:app --reload + +# Server should start on http://localhost:8000 +``` + +**Test the setup:** +- Admin login: http://localhost:8000/admin/login +- Username: `admin` +- Password: `admin123` + +--- + +## Database Reset (Clean Slate) + +Use this when you want to completely reset your database and start fresh. + +⚠️ **WARNING:** This will delete ALL data! Make a backup if needed. + +--- + +### Quick Reset Script (Windows PowerShell) + +**Create `scripts/reset_database.ps1`:** + +```powershell +# scripts/reset_database.ps1 +Write-Host "🗑️ Database Reset Script" -ForegroundColor Cyan +Write-Host "This will delete ALL data!" -ForegroundColor Red +$confirm = Read-Host "Type 'YES' to continue" + +if ($confirm -ne "YES") { + Write-Host "Aborted." -ForegroundColor Yellow + exit +} + +Write-Host "" +Write-Host "Step 1: Backing up current database..." -ForegroundColor Yellow +if (Test-Path wizamart.db) { + $backupName = "wizamart_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').db" + Copy-Item wizamart.db $backupName + Write-Host "✓ Backup created: $backupName" -ForegroundColor Green +} + +Write-Host "" +Write-Host "Step 2: Removing old migrations..." -ForegroundColor Yellow +Remove-Item alembic\versions\*.py -Exclude __init__.py -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force alembic\versions\__pycache__ -ErrorAction SilentlyContinue + +# Ensure __init__.py exists +if (-not (Test-Path alembic\versions\__init__.py)) { + New-Item -Path alembic\versions\__init__.py -ItemType File | Out-Null +} +Write-Host "✓ Old migrations removed" -ForegroundColor Green + +Write-Host "" +Write-Host "Step 3: Deleting database..." -ForegroundColor Yellow +Remove-Item wizamart.db -ErrorAction SilentlyContinue +Write-Host "✓ Database deleted" -ForegroundColor Green + +Write-Host "" +Write-Host "Step 4: Generating new migration..." -ForegroundColor Yellow +alembic revision --autogenerate -m "Initial migration - all tables" +Write-Host "✓ Migration generated" -ForegroundColor Green + +Write-Host "" +Write-Host "Step 5: Applying migration..." -ForegroundColor Yellow +alembic upgrade head +Write-Host "✓ Migration applied" -ForegroundColor Green + +Write-Host "" +Write-Host "Step 6: Verifying tables..." -ForegroundColor Yellow +python -c @" +import sqlite3 +conn = sqlite3.connect('wizamart.db') +cursor = conn.cursor() +cursor.execute('SELECT name FROM sqlite_master WHERE type=\"table\" ORDER BY name;') +tables = [t[0] for t in cursor.fetchall()] +print('\n📊 Tables created:') +for t in tables: + print(f' ✓ {t}') +print(f'\n✅ Total: {len(tables)} tables') +if 'vendor_themes' in tables: + print('✅ vendor_themes table confirmed!') +conn.close() +"@ + +Write-Host "" +Write-Host "Step 7: Seeding initial data..." -ForegroundColor Yellow +python scripts/seed_database.py + +Write-Host "" +Write-Host "✅ Database reset complete!" -ForegroundColor Green +Write-Host "" +Write-Host "Next steps:" -ForegroundColor Cyan +Write-Host " 1. Start server: python -m uvicorn app.main:app --reload" -ForegroundColor White +Write-Host " 2. Login: http://localhost:8000/admin/login" -ForegroundColor White +Write-Host " 3. Username: admin | Password: admin123" -ForegroundColor White +``` + +**Run it:** + +```powershell +# First time only - allow script execution +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +# Run the reset +.\scripts\reset_database.ps1 +``` + +--- + +### Quick Reset Script (Linux/Mac Bash) + +**Create `scripts/reset_database.sh`:** + +```bash +#!/bin/bash +# scripts/reset_database.sh + +echo "🗑️ Database Reset Script" +echo "⚠️ This will delete ALL data!" +read -p "Type 'YES' to continue: " confirm + +if [ "$confirm" != "YES" ]; then + echo "Aborted." + exit 1 +fi + +echo "" +echo "Step 1: Backing up current database..." +if [ -f wizamart.db ]; then + backup_name="wizamart_backup_$(date +%Y%m%d_%H%M%S).db" + cp wizamart.db "$backup_name" + echo "✓ Backup created: $backup_name" +fi + +echo "" +echo "Step 2: Removing old migrations..." +find alembic/versions -type f -name "*.py" ! -name "__init__.py" -delete +rm -rf alembic/versions/__pycache__ +touch alembic/versions/__init__.py +echo "✓ Old migrations removed" + +echo "" +echo "Step 3: Deleting database..." +rm -f wizamart.db +echo "✓ Database deleted" + +echo "" +echo "Step 4: Generating new migration..." +alembic revision --autogenerate -m "Initial migration - all tables" +echo "✓ Migration generated" + +echo "" +echo "Step 5: Applying migration..." +alembic upgrade head +echo "✓ Migration applied" + +echo "" +echo "Step 6: Verifying tables..." +python -c " +import sqlite3 +conn = sqlite3.connect('wizamart.db') +cursor = conn.cursor() +cursor.execute('SELECT name FROM sqlite_master WHERE type=\"table\" ORDER BY name;') +tables = [t[0] for t in cursor.fetchall()] +print('\n📊 Tables created:') +for t in tables: + print(f' ✓ {t}') +print(f'\n✅ Total: {len(tables)} tables') +if 'vendor_themes' in tables: + print('✅ vendor_themes table confirmed!') +conn.close() +" + +echo "" +echo "Step 7: Seeding initial data..." +python scripts/seed_database.py + +echo "" +echo "✅ Database reset complete!" +echo "" +echo "Next steps:" +echo " 1. Start server: python -m uvicorn app.main:app --reload" +echo " 2. Login: http://localhost:8000/admin/login" +echo " 3. Username: admin | Password: admin123" +``` + +**Run it:** + +```bash +chmod +x scripts/reset_database.sh +./scripts/reset_database.sh +``` + +--- + +### Manual Reset Steps + +If you prefer to run commands manually: + +```bash +# 1. Backup (optional) +cp wizamart.db wizamart_backup.db # Windows: copy wizamart.db wizamart_backup.db + +# 2. Remove migrations +rm alembic/versions/*.py # Windows: Remove-Item alembic\versions\*.py -Exclude __init__.py +touch alembic/versions/__init__.py # Windows: New-Item -Path alembic\versions\__init__.py + +# 3. Delete database +rm wizamart.db # Windows: Remove-Item wizamart.db + +# 4. Generate migration +alembic revision --autogenerate -m "Initial migration - all tables" + +# 5. Apply migration +alembic upgrade head + +# 6. Seed data +python scripts/seed_database.py +``` + +--- + +## Verifying Your Setup + +### Check Alembic Status + +```bash +# Check current migration version +alembic current + +# Should show: +# (head) +``` + +### Verify Tables Exist + +**Using Python:** + +```python +import sqlite3 + +conn = sqlite3.connect('wizamart.db') +cursor = conn.cursor() + +# List all tables +cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;") +tables = cursor.fetchall() + +print("📊 Database Tables:") +for table in tables: + print(f" ✓ {table[0]}") + +# Check specific table structure +cursor.execute("PRAGMA table_info(vendor_themes);") +columns = cursor.fetchall() + +print("\n📋 vendor_themes columns:") +for col in columns: + print(f" - {col[1]} ({col[2]})") + +conn.close() +``` + +### Test Database Connection + +```bash +# Start Python shell +python + +# Test connection +from app.core.database import SessionLocal, engine +from sqlalchemy import text + +db = SessionLocal() +result = db.execute(text("SELECT name FROM sqlite_master WHERE type='table';")) +tables = [row[0] for row in result] + +print("✅ Database connection successful!") +print(f"📊 Tables found: {len(tables)}") +for table in tables: + print(f" - {table}") + +db.close() +exit() +``` + +### Test Application Startup + +```bash +# Start server +python -m uvicorn app.main:app --reload + +# Should see: +# INFO: Uvicorn running on http://127.0.0.1:8000 +# INFO: Application startup complete. +``` + +**No errors = successful setup!** + +--- + +## Common Issues & Solutions + +### Issue 1: "No such table" Error + +**Symptom:** +``` +sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: vendor_themes +``` + +**Solution:** + +```bash +# 1. Check if model is imported in alembic/env.py +# Open alembic/env.py and verify: +from models.database.vendor_theme import VendorTheme + +# 2. Regenerate migration +alembic revision --autogenerate -m "Add missing tables" + +# 3. Apply migration +alembic upgrade head +``` + +--- + +### Issue 2: "Target database is not up to date" + +**Symptom:** +``` +ERROR [alembic.util.messaging] Target database is not up to date. +``` + +**Solution:** + +```bash +# Option 1: Stamp to current head +alembic stamp head + +# Option 2: Check and upgrade +alembic current # See current version +alembic upgrade head # Upgrade to latest + +# Option 3: Full reset (see Database Reset section) +``` + +--- + +### Issue 3: Database Locked + +**Symptom:** +``` +sqlite3.OperationalError: database is locked +``` + +**Solution:** + +```bash +# 1. Stop the FastAPI server (Ctrl+C) + +# 2. Kill any Python processes +# Windows: +taskkill /F /IM python.exe + +# Linux/Mac: +pkill python + +# 3. Close any database browser applications + +# 4. Try again +alembic upgrade head +``` + +--- + +### Issue 4: Migration File Conflicts + +**Symptom:** +``` +Multiple head revisions are present +``` + +**Solution:** + +```bash +# Option 1: Merge migrations +alembic merge heads -m "Merge migrations" +alembic upgrade head + +# Option 2: Clean reset (recommended for development) +# See Database Reset section above +``` + +--- + +### Issue 5: Import Errors in alembic/env.py + +**Symptom:** +``` +ModuleNotFoundError: No module named 'models' +``` + +**Solution:** + +Check `alembic/env.py` has correct path setup: + +```python +# alembic/env.py (top of file) +import sys +from pathlib import Path + +# Add project root to Python path +sys.path.append(str(Path(__file__).parents[1])) +``` + +--- + +## Database Migrations + +### Creating New Migrations + +When you add/modify models: + +```bash +# 1. Update your model file (e.g., models/database/vendor_theme.py) + +# 2. Generate migration +alembic revision --autogenerate -m "Add new field to vendor_themes" + +# 3. Review the generated migration +# Check: alembic/versions/_add_new_field_to_vendor_themes.py + +# 4. Apply migration +alembic upgrade head +``` + +--- + +### Migration Best Practices + +✅ **DO:** +- Review generated migrations before applying +- Use descriptive migration messages +- Test migrations on development database first +- Keep migrations small and focused +- Commit migrations to version control + +❌ **DON'T:** +- Edit applied migrations (create new ones instead) +- Delete migration files from version control +- Skip migration review +- Combine unrelated changes in one migration + +--- + +### Rolling Back Migrations + +```bash +# Downgrade one version +alembic downgrade -1 + +# Downgrade to specific revision +alembic downgrade + +# Downgrade to base (empty database) +alembic downgrade base +``` + +--- + +## Seeding Data + +### Development Data Seed + +**Create `scripts/seed_database.py`:** + +```python +# scripts/seed_database.py +""" +Seed the database with initial development data. +""" + +from app.core.database import SessionLocal +from models.database.user import User +from models.database.vendor import Vendor +from app.core.security import get_password_hash +from datetime import datetime, timezone + + +def seed_database(): + """Seed database with initial data.""" + db = SessionLocal() + + try: + print("🌱 Seeding database...") + + # Check if admin already exists + existing_admin = db.query(User).filter(User.username == "admin").first() + if existing_admin: + print("⚠️ Admin user already exists, skipping...") + return + + # Create admin user + admin = User( + username="admin", + email="admin@letzshop.com", + hashed_password=get_password_hash("admin123"), + is_admin=True, + is_active=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + db.add(admin) + db.flush() + print(f"✓ Admin user created (ID: {admin.id})") + + # Create test vendor + vendor = Vendor( + vendor_code="TESTVENDOR", + subdomain="testvendor", + name="Test Vendor", + description="Development test vendor", + owner_user_id=admin.id, + contact_email="contact@testvendor.com", + is_active=True, + is_verified=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + db.add(vendor) + db.flush() + print(f"✓ Test vendor created: {vendor.vendor_code}") + + db.commit() + + print("\n✅ Database seeded successfully!") + print("\n📝 Login Credentials:") + print(" URL: http://localhost:8000/admin/login") + print(" Username: admin") + print(" Password: admin123") + + except Exception as e: + db.rollback() + print(f"❌ Error seeding database: {e}") + raise + finally: + db.close() + + +if __name__ == "__main__": + seed_database() +``` + +**Run it:** + +```bash +python scripts/seed_database.py +``` + +--- + +### Production Data Migration + +For production, create separate seed scripts: + +```python +# scripts/seed_production.py +# - Create only essential admin user +# - No test data +# - Strong passwords from environment variables +``` + +--- + +## Quick Reference + +### Essential Commands + +```bash +# Check migration status +alembic current + +# Generate new migration +alembic revision --autogenerate -m "Description" + +# Apply migrations +alembic upgrade head + +# Rollback one migration +alembic downgrade -1 + +# View migration history +alembic history + +# Seed database +python scripts/seed_database.py + +# Start server +python -m uvicorn app.main:app --reload +``` + +--- + +### File Locations + +``` +project/ +├── alembic/ +│ ├── versions/ # Migration files here +│ │ └── __init__.py +│ ├── env.py # Alembic configuration (import models here!) +│ └── script.py.mako +├── app/ +│ └── core/ +│ └── database.py # Database connection +├── models/ +│ └── database/ # SQLAlchemy models +│ ├── user.py +│ ├── vendor.py +│ ├── vendor_theme.py +│ └── ... +├── scripts/ +│ ├── seed_database.py # Development seed +│ └── reset_database.ps1 # Reset script +├── alembic.ini # Alembic config +├── wizamart.db # SQLite database (gitignored) +└── .env # Environment variables (gitignored) +``` + +--- + +## Getting Help + +### Internal Resources + +- **Architecture Guide:** `docs/PROPER_ARCHITECTURE_GUIDE.md` +- **Exception Handling:** `docs/EXCEPTION_PATTERN_EXPLAINED.md` +- **Admin Feature Guide:** `docs/ADMIN_FEATURE_INTEGRATION_GUIDE.md` + +### External Resources + +- [Alembic Documentation](https://alembic.sqlalchemy.org/) +- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/) +- [FastAPI Documentation](https://fastapi.tiangolo.com/) + +--- + +## Contributing + +When adding new models: + +1. ✅ Create the model in `models/database/` +2. ✅ Import it in `alembic/env.py` +3. ✅ Generate migration: `alembic revision --autogenerate -m "Add XYZ model"` +4. ✅ Review the migration file +5. ✅ Test locally: `alembic upgrade head` +6. ✅ Commit both model and migration files + +--- + +**Last Updated:** 2025-10-27 +**Maintainer:** Development Team +**Questions?** Contact the team lead or check internal documentation. \ No newline at end of file diff --git a/models/database/__init__.py b/models/database/__init__.py index 12d2f464..24ed1b4e 100644 --- a/models/database/__init__.py +++ b/models/database/__init__.py @@ -1,29 +1,38 @@ # models/database/__init__.py """Database models package.""" - +from .admin import AdminAuditLog, AdminNotification, AdminSetting, PlatformAlert, AdminSession from .base import Base -from .customer import Customer -from .order import Order +from .customer import Customer, CustomerAddress +from .order import Order, OrderItem from .user import User from .marketplace_product import MarketplaceProduct from .inventory import Inventory -from .vendor import Vendor +from .vendor import Vendor, Role, VendorUser from .vendor_domain import VendorDomain from .vendor_theme import VendorTheme from .product import Product from .marketplace_import_job import MarketplaceImportJob __all__ = [ + # Admin-specific models + "AdminAuditLog", + "AdminNotification", + "AdminSetting", + "PlatformAlert", + "AdminSession", "Base", "User", - "MarketplaceProduct", "Inventory", "Customer", + "CustomerAddress", "Order", + "OrderItem", "Vendor", + "VendorUser", + "Role", "Product", "MarketplaceImportJob", + "MarketplaceProduct", "VendorDomain", "VendorTheme" ] - diff --git a/scripts/seed_database.py b/scripts/seed_database.py new file mode 100644 index 00000000..4c07013e --- /dev/null +++ b/scripts/seed_database.py @@ -0,0 +1,319 @@ +#!/usr/bin/env python3 +""" +Seed the database with initial development data. + +Creates: +- Admin user (admin/admin123) +- Test vendors (TESTVENDOR, WIZAMART) + +Usage: + python scripts/seed_database.py + +This script is idempotent - safe to run multiple times. +""" + +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 models.database.vendor import Vendor +from middleware.auth import AuthManager +from datetime import datetime, timezone + +# Default credentials +DEFAULT_ADMIN_EMAIL = "admin@letzshop.com" +DEFAULT_ADMIN_USERNAME = "admin" +DEFAULT_ADMIN_PASSWORD = "admin123" # Change in production! + + +def verify_database_ready() -> bool: + """ + Verify that database tables exist. + + Returns: + bool: True if database is ready, False otherwise + """ + try: + with engine.connect() as conn: + from sqlalchemy import text + # Check for users table + result = conn.execute( + text("SELECT name FROM sqlite_master WHERE type='table' AND name='users'") + ) + users_table = result.fetchall() + + # Check for vendors table + result = conn.execute( + text("SELECT name FROM sqlite_master WHERE type='table' AND name='vendors'") + ) + vendors_table = result.fetchall() + + return len(users_table) > 0 and len(vendors_table) > 0 + + except Exception as e: + print(f"❌ Error checking database: {e}") + return False + + +def create_admin_user(db: Session, auth_manager: AuthManager) -> tuple[User, bool]: + """ + Create admin user if it doesn't exist. + + Args: + db: Database session + auth_manager: AuthManager instance for password hashing + + Returns: + tuple: (User object, was_created boolean) + """ + # Check if admin already exists + existing_admin = db.execute( + select(User).where(User.username == DEFAULT_ADMIN_USERNAME) + ).scalar_one_or_none() + + if existing_admin: + return existing_admin, False + + # Create new admin user + admin = User( + email=DEFAULT_ADMIN_EMAIL, + username=DEFAULT_ADMIN_USERNAME, + hashed_password=auth_manager.hash_password(DEFAULT_ADMIN_PASSWORD), + role="admin", + is_active=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + + db.add(admin) + db.flush() # Get the ID + + return admin, True + + +def create_vendor( + db: Session, + vendor_code: str, + name: str, + subdomain: str, + owner_user_id: int, + description: str = None +) -> tuple[Vendor, bool]: + """ + Create vendor if it doesn't exist. + + Args: + db: Database session + vendor_code: Unique vendor code + name: Vendor name + subdomain: Subdomain for the vendor + owner_user_id: ID of the owner user + description: Optional description + + Returns: + tuple: (Vendor object, was_created boolean) + """ + # Check if vendor already exists + existing_vendor = db.execute( + select(Vendor).where(Vendor.vendor_code == vendor_code) + ).scalar_one_or_none() + + if existing_vendor: + return existing_vendor, False + + # Create new vendor + vendor = Vendor( + vendor_code=vendor_code, + subdomain=subdomain, + name=name, + description=description or f"{name} - Development vendor", + owner_user_id=owner_user_id, + contact_email=f"contact@{subdomain}.com", + contact_phone="+352 123 456 789", + website=f"https://{subdomain}.com", + is_active=True, + is_verified=True, + created_at=datetime.now(timezone.utc), + updated_at=datetime.now(timezone.utc) + ) + + db.add(vendor) + db.flush() + + return vendor, True + + +def seed_database(): + """Main seeding function.""" + + print("\n" + "╔" + "═" * 68 + "╗") + print("║" + " " * 18 + "DATABASE SEEDING SCRIPT" + " " * 27 + "║") + print("╚" + "═" * 68 + "╝\n") + + # ======================================================================== + # STEP 1: VERIFY DATABASE + # ======================================================================== + + print("STEP 1: Verifying database...") + + if not verify_database_ready(): + print("\n❌ ERROR: Database not ready!") + print("\n Required tables don't exist yet.") + print(" Please run database migrations first:\n") + print(" alembic upgrade head\n") + sys.exit(1) + + print(" ✓ Database tables verified\n") + + # ======================================================================== + # STEP 2: CREATE ADMIN USER + # ======================================================================== + + db = SessionLocal() + auth_manager = AuthManager() + + try: + print("STEP 2: Creating admin user...") + + admin, admin_created = create_admin_user(db, auth_manager) + + if admin_created: + print(f" ✓ Admin user created (ID: {admin.id})") + print(f" Username: {admin.username}") + print(f" Email: {admin.email}") + else: + print(f" ℹ️ Admin user already exists (ID: {admin.id})") + print(f" Username: {admin.username}") + print(f" Email: {admin.email}") + + # ==================================================================== + # STEP 3: CREATE VENDORS + # ==================================================================== + + print("\nSTEP 3: Creating vendors...") + + # Create TESTVENDOR + test_vendor, test_created = create_vendor( + db, + vendor_code="TESTVENDOR", + name="Test Vendor", + subdomain="testvendor", + owner_user_id=admin.id, + description="Development test vendor for general testing" + ) + + if test_created: + print(f" ✓ TESTVENDOR created (ID: {test_vendor.id})") + else: + print(f" ℹ️ TESTVENDOR already exists (ID: {test_vendor.id})") + + # Create WIZAMART + wizamart, wizamart_created = create_vendor( + db, + vendor_code="WIZAMART", + name="WizaMart", + subdomain="wizamart", + owner_user_id=admin.id, + description="Primary development vendor for theme customization" + ) + + if wizamart_created: + print(f" ✓ WIZAMART created (ID: {wizamart.id})") + else: + print(f" ℹ️ WIZAMART already exists (ID: {wizamart.id})") + + # Commit all changes + db.commit() + + # ==================================================================== + # SUMMARY + # ==================================================================== + + print("\n" + "╔" + "═" * 68 + "╗") + print("║" + " " * 22 + "SEEDING COMPLETE!" + " " * 28 + "║") + print("╚" + "═" * 68 + "╝\n") + + if admin_created or test_created or wizamart_created: + print("✅ New items created:") + if admin_created: + print(" • Admin user") + if test_created: + print(" • TESTVENDOR") + if wizamart_created: + print(" • WIZAMART") + else: + print("ℹ️ All items already existed - no changes made") + + print("\n" + "─" * 70) + print("📝 ADMIN LOGIN CREDENTIALS") + print("─" * 70) + print(f" URL: http://localhost:8000/admin/login") + print(f" Username: {DEFAULT_ADMIN_USERNAME}") + print(f" Password: {DEFAULT_ADMIN_PASSWORD}") + print("─" * 70) + + print("\n" + "─" * 70) + print("🏪 VENDORS") + print("─" * 70) + + vendors = db.execute(select(Vendor)).scalars().all() + for v in vendors: + print(f"\n {v.vendor_code}") + print(f" Name: {v.name}") + print(f" Subdomain: {v.subdomain}.letzshop.com") + print(f" Theme URL: http://localhost:8000/admin/vendors/{v.vendor_code}/theme") + print(f" Verified: {'✓' if v.is_verified else '✗'}") + print(f" Active: {'✓' if v.is_active else '✗'}") + + print("\n" + "─" * 70) + print("🚀 NEXT STEPS") + print("─" * 70) + print(" 1. Start the server:") + print(" python -m uvicorn app.main:app --reload\n") + print(" 2. Login to admin panel:") + print(" http://localhost:8000/admin/login\n") + print(" 3. Test theme editor:") + print(" http://localhost:8000/admin/vendors/WIZAMART/theme\n") + + if admin_created: + print("⚠️ SECURITY: Change the default admin password after first login!") + + print() + + except Exception as e: + db.rollback() + print("\n" + "╔" + "═" * 68 + "╗") + print("║" + " " * 28 + "ERROR" + " " * 35 + "║") + print("╚" + "═" * 68 + "╝\n") + print(f"❌ Failed to seed database: {e}") + print(f"\nError type: {type(e).__name__}") + print("\nCommon issues:") + print(" - Database migrations not run (run: alembic upgrade head)") + print(" - Database connection issues") + print(" - Foreign key constraint violations") + print() + import traceback + traceback.print_exc() + sys.exit(1) + + finally: + db.close() + + +if __name__ == "__main__": + try: + seed_database() + except KeyboardInterrupt: + print("\n\n⚠️ Seeding interrupted by user") + sys.exit(1) + except Exception as e: + print(f"\n❌ Fatal error: {e}") + sys.exit(1) diff --git a/static/admin/js/vendor-theme.js b/static/admin/js/vendor-theme.js index 036f3c1e..ab896793 100644 --- a/static/admin/js/vendor-theme.js +++ b/static/admin/js/vendor-theme.js @@ -1,22 +1,17 @@ -// static/admin/js/vendor-theme.js +// static/admin/js/vendor-theme.js (FIXED VERSION) /** * Vendor Theme Editor - Alpine.js Component * Manages theme customization for vendor shops + * + * REQUIRES: log-config.js to be loaded first */ // ============================================================================ -// LOGGING CONFIGURATION +// LOGGING CONFIGURATION (using centralized logger) // ============================================================================ -const THEME_LOG_LEVEL = 3; // 1=error, 2=warn, 3=info, 4=debug - -const themeLog = { - error: (...args) => THEME_LOG_LEVEL >= 1 && console.error('❌ [THEME ERROR]', ...args), - warn: (...args) => THEME_LOG_LEVEL >= 2 && console.warn('⚠️ [THEME WARN]', ...args), - info: (...args) => THEME_LOG_LEVEL >= 3 && console.info('ℹ️ [THEME INFO]', ...args), - debug: (...args) => THEME_LOG_LEVEL >= 4 && console.log('🔍 [THEME DEBUG]', ...args) -}; - +// Use the pre-configured theme logger from centralized log-config.js +const themeLog = window.LogConfig.loggers.vendorTheme; // ============================================================================ // ALPINE.JS COMPONENT @@ -50,254 +45,278 @@ function adminVendorTheme() { }, fonts: { heading: 'Inter, sans-serif', - body: 'Inter, sans-serif' + body: 'Inter, sans-serif', + size_base: '16px', + size_heading: '2rem' }, layout: { style: 'grid', - header: 'fixed', - product_card: 'modern' + header_position: 'fixed', + product_card_style: 'card', + sidebar_position: 'left' }, branding: { - logo: null, - logo_dark: null, - favicon: null, - banner: null + logo_url: '', + favicon_url: '', + banner_url: '' }, - custom_css: '' + custom_css: '', + social_links: { + facebook: '', + instagram: '', + twitter: '', + linkedin: '' + } }, // Available presets presets: [], + selectedPreset: null, - // ============================================================================ + // ==================================================================== // INITIALIZATION - // ============================================================================ + // ==================================================================== async init() { - themeLog.info('=== VENDOR THEME EDITOR INITIALIZING ==='); + themeLog.info('Initializing vendor theme editor'); - // ✅ CRITICAL: Prevent multiple initializations - if (window._vendorThemeInitialized) { - themeLog.warn('Theme editor already initialized, skipping...'); - return; - } - window._vendorThemeInitialized = true; - - const startTime = Date.now(); - - // Get vendor code from URL - this.vendorCode = this.getVendorCodeFromURL(); - themeLog.info('Vendor code:', this.vendorCode); - - // Load data - await Promise.all([ - this.loadVendorData(), - this.loadTheme(), - this.loadPresets() - ]); - - const duration = Date.now() - startTime; - themeLog.info(`=== THEME EDITOR INITIALIZATION COMPLETE (${duration}ms) ===`); - }, - - // ============================================================================ - // URL HELPERS - // ============================================================================ - - getVendorCodeFromURL() { - const pathParts = window.location.pathname.split('/'); - const vendorIndex = pathParts.indexOf('vendors'); - return pathParts[vendorIndex + 1]; - }, - - // ============================================================================ - // DATA LOADING - // ============================================================================ - - async loadVendorData() { - themeLog.info('Loading vendor data...'); + // Start performance timer + const startTime = performance.now(); try { - const startTime = Date.now(); - const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`); - const duration = Date.now() - startTime; + // Extract vendor code from URL + const urlParts = window.location.pathname.split('/'); + this.vendorCode = urlParts[urlParts.indexOf('vendors') + 1]; - this.vendor = response; - themeLog.info(`Vendor loaded in ${duration}ms:`, this.vendor.name); + themeLog.debug('Vendor code from URL:', this.vendorCode); - } catch (error) { - themeLog.error('Failed to load vendor:', error); - this.error = 'Failed to load vendor data'; - Utils.showToast('Failed to load vendor data', 'error'); - } - }, - - async loadTheme() { - themeLog.info('Loading theme...'); - this.loading = true; - this.error = null; - - try { - const startTime = Date.now(); - const response = await apiClient.get(`/admin/vendor-themes/${this.vendorCode}`); - const duration = Date.now() - startTime; - - if (response) { - // Merge loaded theme with defaults - this.themeData = { - theme_name: response.theme_name || 'default', - colors: { - ...this.themeData.colors, - ...(response.colors || {}) - }, - fonts: { - heading: response.fonts?.heading || this.themeData.fonts.heading, - body: response.fonts?.body || this.themeData.fonts.body - }, - layout: { - style: response.layout?.style || this.themeData.layout.style, - header: response.layout?.header || this.themeData.layout.header, - product_card: response.layout?.product_card || this.themeData.layout.product_card - }, - branding: { - ...this.themeData.branding, - ...(response.branding || {}) - }, - custom_css: response.custom_css || '' - }; - - themeLog.info(`Theme loaded in ${duration}ms:`, this.themeData.theme_name); + if (!this.vendorCode) { + throw new Error('Vendor code not found in URL'); } - } catch (error) { - themeLog.error('Failed to load theme:', error); - this.error = 'Failed to load theme'; - Utils.showToast('Failed to load theme', 'error'); + // Load data in parallel + themeLog.group('Loading theme data'); + await Promise.all([ + this.loadVendor(), + this.loadTheme(), + this.loadPresets() + ]); + + themeLog.groupEnd(); + + // Log performance + const duration = performance.now() - startTime; + window.LogConfig.logPerformance('Theme Editor Init', duration); + + themeLog.info('Theme editor initialized successfully'); + + } catch (error) { + // Use centralized error logger + window.LogConfig.logError(error, 'Theme Editor Init'); + + this.error = error.message || 'Failed to initialize theme editor'; + Utils.showToast(this.error, 'error'); } finally { this.loading = false; } }, - async loadPresets() { - themeLog.info('Loading presets...'); + // ==================================================================== + // DATA LOADING + // ==================================================================== + + async loadVendor() { + themeLog.info('Loading vendor data'); + + const url = `/admin/vendors/${this.vendorCode}`; + window.LogConfig.logApiCall('GET', url, null, 'request'); try { - const startTime = Date.now(); - const response = await apiClient.get('/admin/vendor-themes/presets'); - const duration = Date.now() - startTime; + // ✅ FIX: apiClient returns data directly, not response.data + const response = await apiClient.get(url); - this.presets = response.presets || []; - themeLog.info(`${this.presets.length} presets loaded in ${duration}ms`); + // ✅ Direct assignment - response IS the data + this.vendor = response; + + window.LogConfig.logApiCall('GET', url, this.vendor, 'response'); + themeLog.debug('Vendor loaded:', this.vendor); } catch (error) { - themeLog.warn('Failed to load presets:', error); - // Non-critical error, continue without presets + themeLog.error('Failed to load vendor:', error); + throw error; } }, - // ============================================================================ - // PRESET OPERATIONS - // ============================================================================ + async loadTheme() { + themeLog.info('Loading theme data'); + + const url = `/admin/vendor-themes/${this.vendorCode}`; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + try { + // ✅ FIX: apiClient returns data directly + const response = await apiClient.get(url); + + // Merge with default theme data + this.themeData = { + ...this.themeData, + ...response + }; + + window.LogConfig.logApiCall('GET', url, this.themeData, 'response'); + themeLog.debug('Theme loaded:', this.themeData); + + } catch (error) { + themeLog.warn('Failed to load theme, using defaults:', error); + // Continue with default theme + } + }, + + async loadPresets() { + themeLog.info('Loading theme presets'); + + const url = '/admin/vendor-themes/presets'; + window.LogConfig.logApiCall('GET', url, null, 'request'); + + try { + // ✅ FIX: apiClient returns data directly + const response = await apiClient.get(url); + + // ✅ Access presets directly from response, not response.data.presets + this.presets = response.presets || []; + + window.LogConfig.logApiCall('GET', url, response, 'response'); + themeLog.debug(`Loaded ${this.presets.length} presets`); + + } catch (error) { + themeLog.error('Failed to load presets:', error); + this.presets = []; + } + }, + + // ==================================================================== + // THEME OPERATIONS + // ==================================================================== + + async saveTheme() { + if (this.saving) return; + + themeLog.info('Saving theme changes'); + this.saving = true; + this.error = null; + + const startTime = performance.now(); + + try { + const url = `/admin/vendor-themes/${this.vendorCode}`; + window.LogConfig.logApiCall('PUT', url, this.themeData, 'request'); + + // ✅ FIX: apiClient returns data directly + const response = await apiClient.put(url, this.themeData); + + window.LogConfig.logApiCall('PUT', url, response, 'response'); + + const duration = performance.now() - startTime; + window.LogConfig.logPerformance('Save Theme', duration); + + themeLog.info('Theme saved successfully'); + Utils.showToast('Theme saved successfully', 'success'); + + } catch (error) { + window.LogConfig.logError(error, 'Save Theme'); + this.error = 'Failed to save theme'; + Utils.showToast(this.error, 'error'); + } finally { + this.saving = false; + } + }, async applyPreset(presetName) { themeLog.info(`Applying preset: ${presetName}`); this.saving = true; try { - const startTime = Date.now(); - const response = await apiClient.post( - `/admin/vendor-themes/${this.vendorCode}/preset/${presetName}` - ); - const duration = Date.now() - startTime; + const url = `/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`; + window.LogConfig.logApiCall('POST', url, null, 'request'); + // ✅ FIX: apiClient returns data directly + const response = await apiClient.post(url); + + window.LogConfig.logApiCall('POST', url, response, 'response'); + + // ✅ FIX: Access theme directly from response, not response.data.theme if (response && response.theme) { - // Update theme data with preset this.themeData = { - theme_name: response.theme.theme_name, - colors: response.theme.colors || this.themeData.colors, - fonts: response.theme.fonts || this.themeData.fonts, - layout: response.theme.layout || this.themeData.layout, - branding: response.theme.branding || this.themeData.branding, - custom_css: response.theme.custom_css || '' + ...this.themeData, + ...response.theme }; - - Utils.showToast(`Applied ${presetName} preset successfully`, 'success'); - themeLog.info(`Preset applied in ${duration}ms`); } - } catch (error) { - themeLog.error('Failed to apply preset:', error); - const message = error.response?.data?.detail || 'Failed to apply preset'; - Utils.showToast(message, 'error'); + themeLog.info(`Preset '${presetName}' applied successfully`); + Utils.showToast(`Applied ${presetName} preset`, 'success'); + } catch (error) { + window.LogConfig.logError(error, 'Apply Preset'); + Utils.showToast('Failed to apply preset', 'error'); } finally { this.saving = false; } }, - async resetToDefault() { - if (!confirm('Are you sure you want to reset to default theme? This will discard all customizations.')) { + async resetTheme() { + if (!confirm('Reset theme to default? This cannot be undone.')) { return; } - themeLog.info('Resetting to default theme'); - await this.applyPreset('default'); - }, - - // ============================================================================ - // SAVE OPERATIONS - // ============================================================================ - - async saveTheme() { - themeLog.info('Saving theme:', this.themeData); + themeLog.warn('Resetting theme to default'); this.saving = true; - this.error = null; try { - const startTime = Date.now(); - const response = await apiClient.put( - `/admin/vendor-themes/${this.vendorCode}`, - this.themeData - ); - const duration = Date.now() - startTime; + const url = `/admin/vendor-themes/${this.vendorCode}`; + window.LogConfig.logApiCall('DELETE', url, null, 'request'); - if (response) { - Utils.showToast('Theme saved successfully', 'success'); - themeLog.info(`Theme saved in ${duration}ms`); - } + await apiClient.delete(url); + + window.LogConfig.logApiCall('DELETE', url, null, 'response'); + + // Reload theme data + await this.loadTheme(); + + themeLog.info('Theme reset successfully'); + Utils.showToast('Theme reset to default', 'success'); } catch (error) { - themeLog.error('Failed to save theme:', error); - const message = error.response?.data?.detail || 'Failed to save theme'; - Utils.showToast(message, 'error'); - this.error = message; - + window.LogConfig.logError(error, 'Reset Theme'); + Utils.showToast('Failed to reset theme', 'error'); } finally { this.saving = false; } }, - // ============================================================================ - // HELPER METHODS - // ============================================================================ + // ==================================================================== + // UTILITY METHODS + // ==================================================================== - formatDate(dateString) { - if (!dateString) return '-'; - return Utils.formatDate(dateString); + previewTheme() { + themeLog.debug('Opening theme preview'); + const previewUrl = `/vendor/${this.vendor?.subdomain || this.vendorCode}`; + window.open(previewUrl, '_blank'); }, - getPreviewStyle() { - return { - '--color-primary': this.themeData.colors.primary, - '--color-secondary': this.themeData.colors.secondary, - '--color-accent': this.themeData.colors.accent, - '--color-background': this.themeData.colors.background, - '--color-text': this.themeData.colors.text, - '--color-border': this.themeData.colors.border, - '--font-heading': this.themeData.fonts.heading, - '--font-body': this.themeData.fonts.body, - }; + updateColor(key, value) { + themeLog.debug(`Color updated: ${key} = ${value}`); + this.themeData.colors[key] = value; + }, + + updateFont(type, value) { + themeLog.debug(`Font updated: ${type} = ${value}`); + this.themeData.fonts[type] = value; + }, + + updateLayout(key, value) { + themeLog.debug(`Layout updated: ${key} = ${value}`); + this.themeData.layout[key] = value; } }; } diff --git a/static/shared/js/icons.js b/static/shared/js/icons.js index 402b8a5d..eae56a77 100644 --- a/static/shared/js/icons.js +++ b/static/shared/js/icons.js @@ -69,6 +69,7 @@ const Icons = { 'folder-open': ``, 'download': ``, 'upload': ``, + 'save': ``, // Settings & Tools 'cog': ``, @@ -77,6 +78,10 @@ const Icons = { 'moon': ``, 'sun': ``, + // Design & Theming + 'palette': ``, + 'color-swatch': ``, + // Location 'location-marker': ``, 'globe': ``, diff --git a/static/shared/js/log-config.js b/static/shared/js/log-config.js new file mode 100644 index 00000000..f7a192d4 --- /dev/null +++ b/static/shared/js/log-config.js @@ -0,0 +1,490 @@ +// static/shared/js/log-config.js +/** + * Centralized Logging Configuration for ALL Frontends + * + * This file provides a consistent logging system across: + * - Admin Frontend + * - Vendor Frontend + * - Shop Frontend + * + * Each frontend can customize log levels while sharing the same logging infrastructure. + * + * Usage in any frontend: + * ```javascript + * // Use the global logger + * log.info('Page loaded'); + * log.error('Something went wrong', error); + * + * // Or use a pre-configured logger + * const vendorLog = window.LogConfig.loggers.vendors; + * vendorLog.info('Vendors loaded'); + * + * // Or create a custom logger + * const pageLog = window.LogConfig.createLogger('MY-PAGE', 3); + * pageLog.info('Page initialized'); + * ``` + */ + +// ============================================================================ +// LOG LEVELS +// ============================================================================ + +const LOG_LEVELS = { + ERROR: 1, // Only errors + WARN: 2, // Errors and warnings + INFO: 3, // Errors, warnings, and info (default) + DEBUG: 4 // Everything including debug messages +}; + +// ============================================================================ +// FRONTEND DETECTION +// ============================================================================ + +/** + * Detect which frontend we're in based on URL path + * @returns {string} 'admin' | 'vendor' | 'shop' | 'unknown' + */ +function detectFrontend() { + const path = window.location.pathname; + + if (path.startsWith('/admin')) return 'admin'; + if (path.startsWith('/vendor')) return 'vendor'; + if (path.startsWith('/shop')) return 'shop'; + + return 'unknown'; +} + +// ============================================================================ +// ENVIRONMENT DETECTION +// ============================================================================ + +/** + * Detect environment based on hostname + * @returns {string} 'development' | 'production' + */ +function detectEnvironment() { + const hostname = window.location.hostname; + + // Development environments + if (hostname === 'localhost' || + hostname === '127.0.0.1' || + hostname.startsWith('192.168.') || + hostname.endsWith('.local')) { + return 'development'; + } + + return 'production'; +} + +// ============================================================================ +// LOG LEVEL CONFIGURATION +// ============================================================================ + +/** + * Default log levels per frontend + * Can be overridden by environment + */ +const DEFAULT_LOG_LEVELS = { + admin: { + development: LOG_LEVELS.DEBUG, // Show everything in development + production: LOG_LEVELS.WARN // Only warnings and errors in production + }, + vendor: { + development: LOG_LEVELS.DEBUG, + production: LOG_LEVELS.INFO // Vendors might need more logging + }, + shop: { + development: LOG_LEVELS.DEBUG, + production: LOG_LEVELS.ERROR // Shop frontend: minimal logging in production + }, + unknown: { + development: LOG_LEVELS.DEBUG, + production: LOG_LEVELS.WARN + } +}; + +// ============================================================================ +// ACTIVE LOG LEVEL DETERMINATION +// ============================================================================ + +const CURRENT_FRONTEND = detectFrontend(); +const CURRENT_ENVIRONMENT = detectEnvironment(); +const ACTIVE_LOG_LEVEL = DEFAULT_LOG_LEVELS[CURRENT_FRONTEND][CURRENT_ENVIRONMENT]; + +// ============================================================================ +// LOGGING UTILITIES +// ============================================================================ + +/** + * Create a logger with a specific prefix and log level + * + * @param {string} prefix - Logger prefix (e.g., 'VENDORS', 'THEME', 'PRODUCTS') + * @param {number} level - Log level (1-4, defaults to ACTIVE_LOG_LEVEL) + * @param {string} context - Optional frontend context (defaults to CURRENT_FRONTEND) + * @returns {Object} Logger object with error, warn, info, debug methods + */ +function createLogger(prefix = 'APP', level = ACTIVE_LOG_LEVEL, context = CURRENT_FRONTEND) { + const frontendPrefix = context.toUpperCase(); + const formatPrefix = (emoji, label) => `${emoji} [${frontendPrefix}:${prefix} ${label}]`; + + return { + error: (...args) => { + if (level >= LOG_LEVELS.ERROR) { + console.error(formatPrefix('❌', 'ERROR'), ...args); + } + }, + + warn: (...args) => { + if (level >= LOG_LEVELS.WARN) { + console.warn(formatPrefix('⚠️', 'WARN'), ...args); + } + }, + + info: (...args) => { + if (level >= LOG_LEVELS.INFO) { + console.info(formatPrefix('ℹ️', 'INFO'), ...args); + } + }, + + debug: (...args) => { + if (level >= LOG_LEVELS.DEBUG) { + console.log(formatPrefix('🔍', 'DEBUG'), ...args); + } + }, + + // Additional utility methods + group: (label) => { + if (level >= LOG_LEVELS.INFO) { + console.group(formatPrefix('📂', 'GROUP') + ' ' + label); + } + }, + + groupEnd: () => { + if (level >= LOG_LEVELS.INFO) { + console.groupEnd(); + } + }, + + table: (data) => { + if (level >= LOG_LEVELS.DEBUG) { + console.table(data); + } + }, + + time: (label) => { + if (level >= LOG_LEVELS.DEBUG) { + console.time(formatPrefix('⏱️', 'TIME') + ' ' + label); + } + }, + + timeEnd: (label) => { + if (level >= LOG_LEVELS.DEBUG) { + console.timeEnd(formatPrefix('⏱️', 'TIME') + ' ' + label); + } + } + }; +} + +// ============================================================================ +// DEFAULT GLOBAL LOGGER +// ============================================================================ + +/** + * Default logger for general operations + * Automatically prefixed with current frontend + */ +const log = createLogger('APP', ACTIVE_LOG_LEVEL); + +// ============================================================================ +// PRE-CONFIGURED LOGGERS FOR ADMIN FRONTEND +// ============================================================================ + +const adminLoggers = { + // Vendor management + vendors: createLogger('VENDORS', ACTIVE_LOG_LEVEL), + vendorTheme: createLogger('THEME', ACTIVE_LOG_LEVEL), + vendorUsers: createLogger('VENDOR-USERS', ACTIVE_LOG_LEVEL), + + // Product management + products: createLogger('PRODUCTS', ACTIVE_LOG_LEVEL), + inventory: createLogger('INVENTORY', ACTIVE_LOG_LEVEL), + + // Order management + orders: createLogger('ORDERS', ACTIVE_LOG_LEVEL), + + // User management + users: createLogger('USERS', ACTIVE_LOG_LEVEL), + + // Admin operations + audit: createLogger('AUDIT', ACTIVE_LOG_LEVEL), + dashboard: createLogger('DASHBOARD', ACTIVE_LOG_LEVEL), + + // Import operations + imports: createLogger('IMPORTS', ACTIVE_LOG_LEVEL) +}; + +// ============================================================================ +// PRE-CONFIGURED LOGGERS FOR VENDOR FRONTEND +// ============================================================================ + +const vendorLoggers = { + // Vendor dashboard + dashboard: createLogger('DASHBOARD', ACTIVE_LOG_LEVEL), + + // Product management + products: createLogger('PRODUCTS', ACTIVE_LOG_LEVEL), + inventory: createLogger('INVENTORY', ACTIVE_LOG_LEVEL), + + // Order management + orders: createLogger('ORDERS', ACTIVE_LOG_LEVEL), + + // Theme customization + theme: createLogger('THEME', ACTIVE_LOG_LEVEL), + + // Settings + settings: createLogger('SETTINGS', ACTIVE_LOG_LEVEL), + + // Analytics + analytics: createLogger('ANALYTICS', ACTIVE_LOG_LEVEL) +}; + +// ============================================================================ +// PRE-CONFIGURED LOGGERS FOR SHOP FRONTEND +// ============================================================================ + +const shopLoggers = { + // Product browsing + catalog: createLogger('CATALOG', ACTIVE_LOG_LEVEL), + product: createLogger('PRODUCT', ACTIVE_LOG_LEVEL), + search: createLogger('SEARCH', ACTIVE_LOG_LEVEL), + + // Shopping cart + cart: createLogger('CART', ACTIVE_LOG_LEVEL), + checkout: createLogger('CHECKOUT', ACTIVE_LOG_LEVEL), + + // User account + account: createLogger('ACCOUNT', ACTIVE_LOG_LEVEL), + orders: createLogger('ORDERS', ACTIVE_LOG_LEVEL), + + // Wishlist + wishlist: createLogger('WISHLIST', ACTIVE_LOG_LEVEL) +}; + +// ============================================================================ +// SMART LOGGER SELECTION +// ============================================================================ + +/** + * Get the appropriate logger set for current frontend + */ +function getLoggers() { + switch (CURRENT_FRONTEND) { + case 'admin': + return adminLoggers; + case 'vendor': + return vendorLoggers; + case 'shop': + return shopLoggers; + default: + return {}; // Empty object, use createLogger instead + } +} + +// Export frontend-specific loggers +const loggers = getLoggers(); + +// ============================================================================ +// API CALL LOGGING +// ============================================================================ + +/** + * Log API calls with consistent formatting + * + * @param {string} method - HTTP method (GET, POST, PUT, DELETE) + * @param {string} url - API endpoint URL + * @param {*} data - Request/response data (optional) + * @param {string} status - 'request' or 'response' + */ +function logApiCall(method, url, data = null, status = 'request') { + const apiLogger = createLogger('API', ACTIVE_LOG_LEVEL); + + const emoji = status === 'request' ? '📤' : '📥'; + const message = `${emoji} ${method} ${url}`; + + if (ACTIVE_LOG_LEVEL >= LOG_LEVELS.DEBUG) { + if (data) { + apiLogger.debug(message, data); + } else { + apiLogger.debug(message); + } + } else if (ACTIVE_LOG_LEVEL >= LOG_LEVELS.INFO) { + apiLogger.info(message); + } +} + +// ============================================================================ +// ERROR LOGGING +// ============================================================================ + +/** + * Log errors with stack traces + * + * @param {Error} error - Error object + * @param {string} context - Context where error occurred + */ +function logError(error, context = 'Unknown') { + const errorLogger = createLogger('ERROR', ACTIVE_LOG_LEVEL); + + errorLogger.error(`Error in ${context}:`, error.message); + + if (ACTIVE_LOG_LEVEL >= LOG_LEVELS.DEBUG && error.stack) { + console.error('Stack trace:', error.stack); + } +} + +// ============================================================================ +// PERFORMANCE LOGGING +// ============================================================================ + +/** + * Log performance metrics + * + * @param {string} operation - Operation name + * @param {number} duration - Duration in milliseconds + */ +function logPerformance(operation, duration) { + const perfLogger = createLogger('PERF', ACTIVE_LOG_LEVEL); + + if (ACTIVE_LOG_LEVEL >= LOG_LEVELS.DEBUG) { + const emoji = duration < 100 ? '⚡' : duration < 500 ? '⏱️' : '🐌'; + perfLogger.debug(`${emoji} ${operation} took ${duration}ms`); + } +} + +// ============================================================================ +// EXPORTS (for modules) +// ============================================================================ + +// For pages that use module imports +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + LOG_LEVELS, + log, + loggers, + createLogger, + logApiCall, + logError, + logPerformance, + detectFrontend, + detectEnvironment + }; +} + +// ============================================================================ +// GLOBAL ACCESS (for inline scripts) +// ============================================================================ + +// Make available globally for inline scripts +window.LogConfig = { + LOG_LEVELS, + log, + loggers, + createLogger, + logApiCall, + logError, + logPerformance, + + // Expose frontend/environment info + frontend: CURRENT_FRONTEND, + environment: CURRENT_ENVIRONMENT, + logLevel: ACTIVE_LOG_LEVEL +}; + +// ============================================================================ +// INITIALIZATION +// ============================================================================ + +// Log that logging system is initialized +if (ACTIVE_LOG_LEVEL >= LOG_LEVELS.INFO) { + const frontendName = CURRENT_FRONTEND.charAt(0).toUpperCase() + CURRENT_FRONTEND.slice(1); + const envName = CURRENT_ENVIRONMENT.charAt(0).toUpperCase() + CURRENT_ENVIRONMENT.slice(1); + const levelName = Object.keys(LOG_LEVELS).find(k => LOG_LEVELS[k] === ACTIVE_LOG_LEVEL); + + console.log( + `%c🎛️ ${frontendName} Frontend Logging System Initialized`, + 'font-weight: bold; font-size: 14px; color: #6366f1;' + ); + console.log( + `%c Environment: ${envName}`, + 'color: #8b5cf6;' + ); + console.log( + `%c Log Level: ${ACTIVE_LOG_LEVEL} (${levelName})`, + 'color: #8b5cf6;' + ); +} + +// ============================================================================ +// USAGE EXAMPLES (for developers) +// ============================================================================ + +/* + +EXAMPLE 1: Use global logger +============================= +window.LogConfig.log.info('Application started'); +window.LogConfig.log.error('Failed to load data', error); + + +EXAMPLE 2: Use pre-configured logger (RECOMMENDED) +=================================================== +// In admin frontend +const themeLog = window.LogConfig.loggers.vendorTheme; +themeLog.info('Theme editor loaded'); + +// In vendor frontend +const dashLog = window.LogConfig.loggers.dashboard; +dashLog.info('Dashboard initialized'); + +// In shop frontend +const cartLog = window.LogConfig.loggers.cart; +cartLog.info('Cart updated'); + + +EXAMPLE 3: Create custom logger +================================ +const myLog = window.LogConfig.createLogger('MY-FEATURE', 4); +myLog.info('Feature initialized'); + + +EXAMPLE 4: Check current frontend +================================== +if (window.LogConfig.frontend === 'admin') { + // Admin-specific code +} + + +EXAMPLE 5: API call logging +============================ +window.LogConfig.logApiCall('GET', '/api/vendors', null, 'request'); +const data = await apiClient.get('/api/vendors'); +window.LogConfig.logApiCall('GET', '/api/vendors', data, 'response'); + + +EXAMPLE 6: Performance logging +=============================== +const start = performance.now(); +await loadData(); +const duration = performance.now() - start; +window.LogConfig.logPerformance('Load Data', duration); + + +EXAMPLE 7: Error logging +========================= +try { + await saveData(); +} catch (error) { + window.LogConfig.logError(error, 'Save Operation'); +} + +*/ \ No newline at end of file