revamping frontend logging system and reorganising documentation
This commit is contained in:
6
.env
6
.env
@@ -6,9 +6,9 @@ DESCRIPTION=Advanced product management system with JWT authentication
|
|||||||
VERSION=0.0.1
|
VERSION=0.0.1
|
||||||
|
|
||||||
# Database Configuration
|
# 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:
|
# For development, you can use SQLite:
|
||||||
DATABASE_URL=sqlite:///./ecommerce.db
|
DATABASE_URL=sqlite:///./wizamart.db
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
# .env.development
|
# .env.development
|
||||||
@@ -49,5 +49,5 @@ SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
|
|||||||
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
||||||
|
|
||||||
# DNS verification
|
# DNS verification
|
||||||
DNS_VERIFICATION_PREFIX=_letzshop-verify
|
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
||||||
DNS_VERIFICATION_TTL=3600
|
DNS_VERIFICATION_TTL=3600
|
||||||
@@ -6,9 +6,9 @@ DESCRIPTION=Advanced product management system with JWT authentication
|
|||||||
VERSION=0.0.1
|
VERSION=0.0.1
|
||||||
|
|
||||||
# Database Configuration
|
# 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:
|
# For development, you can use SQLite:
|
||||||
DATABASE_URL=sqlite:///./ecommerce.db
|
DATABASE_URL=sqlite:///./wizamart.db
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
# .env.development
|
# .env.development
|
||||||
@@ -49,5 +49,5 @@ SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
|
|||||||
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
||||||
|
|
||||||
# DNS verification
|
# DNS verification
|
||||||
DNS_VERIFICATION_PREFIX=_letzshop-verify
|
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
||||||
DNS_VERIFICATION_TTL=3600
|
DNS_VERIFICATION_TTL=3600
|
||||||
171
alembic/env.py
171
alembic/env.py
@@ -1,4 +1,15 @@
|
|||||||
# alembic/env.py
|
# 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 os
|
||||||
import sys
|
import sys
|
||||||
from logging.config import fileConfig
|
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 app.core.config import settings
|
||||||
from models.database.base import Base
|
from models.database.base import Base
|
||||||
|
|
||||||
# === IMPORTANT: Import all your DATABASE models here ===
|
# ============================================================================
|
||||||
# Only import SQLAlchemy models, not Pydantic API models
|
# IMPORT ALL DATABASE MODELS
|
||||||
print("[ALEMBIC] Importing 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:
|
try:
|
||||||
from models.database.user import User
|
from models.database.user import User
|
||||||
|
|
||||||
print(" ✓ User model imported")
|
print(" ✓ User model imported")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f" ✗ User model failed: {e}")
|
print(f" ✗ User model failed: {e}")
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# VENDOR MODELS
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
try:
|
try:
|
||||||
from models.database.vendor import Vendor, VendorUser, Role
|
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:
|
except ImportError as e:
|
||||||
print(f" ✗ Vendor models failed: {e}")
|
print(f" ✗ Vendor models failed: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from models.database.vendor_domain import VendorDomain
|
from models.database.vendor_domain import VendorDomain
|
||||||
|
|
||||||
print(" ✓ VendorDomain model imported")
|
print(" ✓ VendorDomain model imported")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f" ✗ VendorDomain model failed: {e}")
|
print(f" ✗ VendorDomain model failed: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from models.database.vendor_theme import VendorTheme
|
from models.database.vendor_theme import VendorTheme
|
||||||
|
|
||||||
print(" ✓ VendorTheme model imported")
|
print(" ✓ VendorTheme model imported")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f" ✗ VendorTheme model failed: {e}")
|
print(f" ✗ VendorTheme model failed: {e}")
|
||||||
|
|
||||||
# Product models
|
# ----------------------------------------------------------------------------
|
||||||
try:
|
# PRODUCT MODELS
|
||||||
from models.database.marketplace_product import MarketplaceProduct
|
# ----------------------------------------------------------------------------
|
||||||
print(" ✓ MarketplaceProduct model imported")
|
|
||||||
except ImportError as e:
|
|
||||||
print(f" ✗ MarketplaceProduct model failed: {e}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from models.database.product import Product
|
from models.database.product import Product
|
||||||
|
|
||||||
print(" ✓ Product model imported")
|
print(" ✓ Product model imported")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f" ✗ Product model failed: {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:
|
try:
|
||||||
from models.database.inventory import Inventory
|
from models.database.inventory import Inventory
|
||||||
|
|
||||||
print(" ✓ Inventory model imported")
|
print(" ✓ Inventory model imported")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f" ✗ Inventory model failed: {e}")
|
print(f" ✗ Inventory model failed: {e}")
|
||||||
|
|
||||||
# Marketplace imports
|
# ----------------------------------------------------------------------------
|
||||||
|
# MARKETPLACE IMPORT
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
try:
|
try:
|
||||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||||
|
|
||||||
print(" ✓ MarketplaceImportJob model imported")
|
print(" ✓ MarketplaceImportJob model imported")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
print(f" ✗ MarketplaceImportJob model failed: {e}")
|
print(f" ✗ MarketplaceImportJob model failed: {e}")
|
||||||
|
|
||||||
# Customer models
|
# ----------------------------------------------------------------------------
|
||||||
|
# CUSTOMER MODELS
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
try:
|
try:
|
||||||
from models.database.customer import Customer, CustomerAddress
|
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:
|
except ImportError as e:
|
||||||
print(f" ✗ Customer models failed: {e}")
|
print(f" ✗ Customer models failed: {e}")
|
||||||
|
|
||||||
# Order models
|
# ----------------------------------------------------------------------------
|
||||||
|
# ORDER MODELS
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
try:
|
try:
|
||||||
from models.database.order import Order, OrderItem
|
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:
|
except ImportError as e:
|
||||||
print(f" ✗ Order models failed: {e}")
|
print(f" ✗ Order models failed: {e}")
|
||||||
|
|
||||||
# Payment models (if you have them)
|
# ============================================================================
|
||||||
try:
|
# SUMMARY
|
||||||
from models.database.payment import Payment
|
# ============================================================================
|
||||||
print(" ✓ Payment model imported")
|
print("=" * 70)
|
||||||
except ImportError as e:
|
print(f"[ALEMBIC] Model import completed")
|
||||||
print(f" - Payment model not found (optional)")
|
print(f"[ALEMBIC] Tables detected in metadata:")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
# Shipping models (if you have them)
|
if Base.metadata.tables:
|
||||||
try:
|
for i, table_name in enumerate(sorted(Base.metadata.tables.keys()), 1):
|
||||||
from models.database.shipping import ShippingAddress
|
print(f" {i:2d}. {table_name}")
|
||||||
print(" ✓ Shipping model imported")
|
print("=" * 70)
|
||||||
except ImportError as e:
|
print(f"[ALEMBIC] Total tables: {len(Base.metadata.tables)}")
|
||||||
print(f" - Shipping model not found (optional)")
|
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("=" * 70)
|
||||||
print(f"[ALEMBIC] Tables found: {list(Base.metadata.tables.keys())}")
|
print()
|
||||||
print(f"[ALEMBIC] Total tables to create: {len(Base.metadata.tables)}\n")
|
|
||||||
|
# ============================================================================
|
||||||
|
# ALEMBIC CONFIGURATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
# Alembic Config object
|
# Alembic Config object
|
||||||
config = context.config
|
config = context.config
|
||||||
@@ -109,11 +187,20 @@ config.set_main_option("sqlalchemy.url", settings.database_url)
|
|||||||
if config.config_file_name is not None:
|
if config.config_file_name is not None:
|
||||||
fileConfig(config.config_file_name)
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# Set target metadata from Base
|
||||||
target_metadata = Base.metadata
|
target_metadata = Base.metadata
|
||||||
|
|
||||||
|
|
||||||
def run_migrations_offline() -> None:
|
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")
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
context.configure(
|
context.configure(
|
||||||
url=url,
|
url=url,
|
||||||
@@ -127,7 +214,12 @@ def run_migrations_offline() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def run_migrations_online() -> 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(
|
connectable = engine_from_config(
|
||||||
config.get_section(config.config_ini_section, {}),
|
config.get_section(config.config_ini_section, {}),
|
||||||
prefix="sqlalchemy.",
|
prefix="sqlalchemy.",
|
||||||
@@ -135,12 +227,19 @@ def run_migrations_online() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
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():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# MAIN EXECUTION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
if context.is_offline_mode():
|
if context.is_offline_mode():
|
||||||
run_migrations_offline()
|
run_migrations_offline()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""initial_schema_with_proper_relationships
|
"""Initial migration - all tables
|
||||||
|
|
||||||
Revision ID: 6fe45d3d84c4
|
Revision ID: 4951b2e50581
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2025-10-07 22:11:56.036486
|
Create Date: 2025-10-27 22:28:33.137564
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
@@ -12,7 +12,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = '6fe45d3d84c4'
|
revision: str = '4951b2e50581'
|
||||||
down_revision: Union[str, None] = None
|
down_revision: Union[str, None] = None
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: 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_email'), 'users', ['email'], unique=True)
|
||||||
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
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_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',
|
op.create_table('vendors',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('vendor_code', sa.String(), 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('name', sa.String(), nullable=False),
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
sa.Column('description', sa.Text(), nullable=True),
|
||||||
sa.Column('owner_user_id', sa.Integer(), nullable=False),
|
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_email', sa.String(), nullable=True),
|
||||||
sa.Column('contact_phone', sa.String(), nullable=True),
|
sa.Column('contact_phone', sa.String(), nullable=True),
|
||||||
sa.Column('website', sa.String(), nullable=True),
|
sa.Column('website', sa.String(), nullable=True),
|
||||||
@@ -203,6 +308,54 @@ def upgrade() -> None:
|
|||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_index(op.f('ix_roles_id'), 'roles', ['id'], unique=False)
|
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',
|
op.create_table('customer_addresses',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('vendor_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_table('inventory')
|
||||||
op.drop_index(op.f('ix_customer_addresses_id'), table_name='customer_addresses')
|
op.drop_index(op.f('ix_customer_addresses_id'), table_name='customer_addresses')
|
||||||
op.drop_table('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_index(op.f('ix_roles_id'), table_name='roles')
|
||||||
op.drop_table('roles')
|
op.drop_table('roles')
|
||||||
op.drop_index(op.f('ix_products_id'), table_name='products')
|
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_subdomain'), table_name='vendors')
|
||||||
op.drop_index(op.f('ix_vendors_id'), table_name='vendors')
|
op.drop_index(op.f('ix_vendors_id'), table_name='vendors')
|
||||||
op.drop_table('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_username'), table_name='users')
|
||||||
op.drop_index(op.f('ix_users_id'), 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')
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||||
@@ -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 ###
|
|
||||||
0
alembic/versions/__init__.py
Normal file
0
alembic/versions/__init__.py
Normal file
@@ -266,14 +266,14 @@ def verify_domain_ownership(
|
|||||||
Verify domain ownership via DNS TXT record (Admin only).
|
Verify domain ownership via DNS TXT record (Admin only).
|
||||||
|
|
||||||
**Verification Process:**
|
**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
|
2. Checks if verification token matches
|
||||||
3. If found, marks domain as verified
|
3. If found, marks domain as verified
|
||||||
|
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
- Vendor must have added TXT record to their DNS
|
- Vendor must have added TXT record to their DNS
|
||||||
- DNS propagation may take 5-15 minutes
|
- 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:**
|
**After verification:**
|
||||||
- Domain can be activated
|
- Domain can be activated
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class Settings(BaseSettings):
|
|||||||
auto_provision_ssl: bool = False # Set to True if using automated SSL
|
auto_provision_ssl: bool = False # Set to True if using automated SSL
|
||||||
|
|
||||||
# DNS verification
|
# DNS verification
|
||||||
dns_verification_prefix: str = "_letzshop-verify"
|
dns_verification_prefix: str = "_wizamart-verify"
|
||||||
dns_verification_ttl: int = 3600
|
dns_verification_ttl: int = 3600
|
||||||
|
|
||||||
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
|
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ class VendorDomainService:
|
|||||||
Verify domain ownership via DNS TXT record.
|
Verify domain ownership via DNS TXT record.
|
||||||
|
|
||||||
The vendor must add a TXT record:
|
The vendor must add a TXT record:
|
||||||
Name: _letzshop-verify.{domain}
|
Name: _wizamart-verify.{domain}
|
||||||
Value: {verification_token}
|
Value: {verification_token}
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -313,7 +313,7 @@ class VendorDomainService:
|
|||||||
# Query DNS TXT records
|
# Query DNS TXT records
|
||||||
try:
|
try:
|
||||||
txt_records = dns.resolver.resolve(
|
txt_records = dns.resolver.resolve(
|
||||||
f"_letzshop-verify.{domain.domain}",
|
f"_wizamart-verify.{domain.domain}",
|
||||||
'TXT'
|
'TXT'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -339,7 +339,7 @@ class VendorDomainService:
|
|||||||
except dns.resolver.NXDOMAIN:
|
except dns.resolver.NXDOMAIN:
|
||||||
raise DomainVerificationFailedException(
|
raise DomainVerificationFailedException(
|
||||||
domain.domain,
|
domain.domain,
|
||||||
f"DNS record _letzshop-verify.{domain.domain} not found"
|
f"DNS record _wizamart-verify.{domain.domain} not found"
|
||||||
)
|
)
|
||||||
except dns.resolver.NoAnswer:
|
except dns.resolver.NoAnswer:
|
||||||
raise DomainVerificationFailedException(
|
raise DomainVerificationFailedException(
|
||||||
@@ -394,7 +394,7 @@ class VendorDomainService:
|
|||||||
},
|
},
|
||||||
"txt_record": {
|
"txt_record": {
|
||||||
"type": "TXT",
|
"type": "TXT",
|
||||||
"name": "_letzshop-verify",
|
"name": "_wizamart-verify",
|
||||||
"value": domain.verification_token,
|
"value": domain.verification_token,
|
||||||
"ttl": 3600
|
"ttl": 3600
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<!-- Core Scripts - ORDER MATTERS! -->
|
<!-- Core Scripts - ORDER MATTERS! -->
|
||||||
|
|
||||||
<!-- 1. FIRST: Log Configuration -->
|
<!-- 1. FIRST: Log Configuration -->
|
||||||
<script src="{{ url_for('static', path='admin/js/log-config.js') }}"></script>
|
<script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
|
||||||
|
|
||||||
<!-- 2. SECOND: Icons (before Alpine.js) -->
|
<!-- 2. SECOND: Icons (before Alpine.js) -->
|
||||||
<script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
|
<script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
|
||||||
|
|||||||
@@ -243,7 +243,7 @@
|
|||||||
<!-- Typography Section -->
|
<!-- Typography Section -->
|
||||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||||
<span x-html="$icon('document-text', 'inline w-5 h-5 mr-2')"></span>
|
<span x-html="$icon('document', 'inline w-5 h-5 mr-2')"></span>
|
||||||
Typography
|
Typography
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
|
|||||||
562
docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md
Normal file
562
docs/__REVAMPING/FRONTEND/FRONTEND_ALPINE_PAGE_TEMPLATE.md
Normal file
@@ -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 %}
|
||||||
|
<script src="{{ url_for('static', path='admin/js/your-page.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Done! ✅
|
||||||
@@ -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! 🎉
|
||||||
@@ -402,7 +402,7 @@ Value: 123.45.67.89
|
|||||||
TTL: 3600
|
TTL: 3600
|
||||||
|
|
||||||
Type: TXT
|
Type: TXT
|
||||||
Name: _letzshop-verify
|
Name: _wizamart-verify
|
||||||
Value: abc123xyz (verification token from your platform)
|
Value: abc123xyz (verification token from your platform)
|
||||||
TTL: 3600
|
TTL: 3600
|
||||||
```
|
```
|
||||||
@@ -161,7 +161,7 @@ To prevent domain hijacking, verify the vendor owns the domain:
|
|||||||
2. System generates verification token: `abc123xyz`
|
2. System generates verification token: `abc123xyz`
|
||||||
3. Vendor adds DNS TXT record:
|
3. Vendor adds DNS TXT record:
|
||||||
```
|
```
|
||||||
Name: _letzshop-verify.customdomain1.com
|
Name: _wizamart-verify.customdomain1.com
|
||||||
Type: TXT
|
Type: TXT
|
||||||
Value: abc123xyz
|
Value: abc123xyz
|
||||||
```
|
```
|
||||||
@@ -177,7 +177,7 @@ def verify_domain(domain_id: int, db: Session):
|
|||||||
|
|
||||||
# Query DNS for TXT record
|
# Query DNS for TXT record
|
||||||
txt_records = dns.resolver.resolve(
|
txt_records = dns.resolver.resolve(
|
||||||
f"_letzshop-verify.{domain.domain}",
|
f"_wizamart-verify.{domain.domain}",
|
||||||
'TXT'
|
'TXT'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ Name: @
|
|||||||
Value: 123.45.67.89 (your server IP)
|
Value: 123.45.67.89 (your server IP)
|
||||||
|
|
||||||
Type: TXT
|
Type: TXT
|
||||||
Name: _letzshop-verify
|
Name: _wizamart-verify
|
||||||
Value: abc123xyz
|
Value: abc123xyz
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ pip install dnspython
|
|||||||
import dns.resolver
|
import dns.resolver
|
||||||
|
|
||||||
# Test querying TXT record
|
# 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:
|
for txt in answers:
|
||||||
print(txt.to_text())
|
print(txt.to_text())
|
||||||
```
|
```
|
||||||
@@ -259,7 +259,7 @@ TTL: 3600
|
|||||||
**Verification TXT Record:**
|
**Verification TXT Record:**
|
||||||
```
|
```
|
||||||
Type: TXT
|
Type: TXT
|
||||||
Name: _letzshop-verify
|
Name: _wizamart-verify
|
||||||
Value: [token from step 9.1]
|
Value: [token from step 9.1]
|
||||||
TTL: 3600
|
TTL: 3600
|
||||||
```
|
```
|
||||||
@@ -351,7 +351,7 @@ HAVING COUNT(*) > 1;
|
|||||||
**Check DNS propagation:**
|
**Check DNS propagation:**
|
||||||
```bash
|
```bash
|
||||||
# Check if TXT record exists
|
# Check if TXT record exists
|
||||||
dig _letzshop-verify.customdomain1.com TXT
|
dig _wizamart-verify.customdomain1.com TXT
|
||||||
|
|
||||||
# Should show verification token
|
# Should show verification token
|
||||||
```
|
```
|
||||||
@@ -203,7 +203,7 @@ When a vendor wants to use `customdomain1.com`:
|
|||||||
Value: 123.45.67.89 (your server IP)
|
Value: 123.45.67.89 (your server IP)
|
||||||
|
|
||||||
2. Verification TXT Record:
|
2. Verification TXT Record:
|
||||||
Name: _letzshop-verify
|
Name: _wizamart-verify
|
||||||
Value: [token from your platform]
|
Value: [token from your platform]
|
||||||
|
|
||||||
3. Wait 5-15 minutes for DNS propagation
|
3. Wait 5-15 minutes for DNS propagation
|
||||||
@@ -331,7 +331,7 @@ Step 2: Get Instructions
|
|||||||
┌────────────────────────────────────┐
|
┌────────────────────────────────────┐
|
||||||
│ System returns instructions: │
|
│ System returns instructions: │
|
||||||
│ "Add TXT record: │
|
│ "Add TXT record: │
|
||||||
│ _letzshop-verify.myshop.com │
|
│ _wizamart-verify.myshop.com │
|
||||||
│ Value: abc123..." │
|
│ Value: abc123..." │
|
||||||
└────────────────────────────────────┘
|
└────────────────────────────────────┘
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ Step 3: Vendor Adds DNS Record
|
|||||||
▼
|
▼
|
||||||
┌────────────────────────────────────┐
|
┌────────────────────────────────────┐
|
||||||
│ DNS Provider (GoDaddy/etc) │
|
│ DNS Provider (GoDaddy/etc) │
|
||||||
│ _letzshop-verify.myshop.com TXT │
|
│ _wizamart-verify.myshop.com TXT │
|
||||||
│ "abc123..." │
|
│ "abc123..." │
|
||||||
└────────────────────────────────────┘
|
└────────────────────────────────────┘
|
||||||
|
|
||||||
@@ -356,14 +356,14 @@ GET /api/v1/admin/vendors/domains/1/verification-instructions
|
|||||||
},
|
},
|
||||||
"txt_record": {
|
"txt_record": {
|
||||||
"type": "TXT",
|
"type": "TXT",
|
||||||
"name": "_letzshop-verify",
|
"name": "_wizamart-verify",
|
||||||
"value": "abc123xyz...",
|
"value": "abc123xyz...",
|
||||||
"ttl": 3600
|
"ttl": 3600
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Step 2: Vendor adds DNS record
|
# Step 2: Vendor adds DNS record
|
||||||
# _letzshop-verify.myshop.com TXT "abc123xyz..."
|
# _wizamart-verify.myshop.com TXT "abc123xyz..."
|
||||||
|
|
||||||
# Step 3: Verify domain
|
# Step 3: Verify domain
|
||||||
POST /api/v1/admin/vendors/domains/1/verify
|
POST /api/v1/admin/vendors/domains/1/verify
|
||||||
@@ -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 %}
|
|
||||||
<!-- Your page content -->
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_scripts %}
|
|
||||||
{# ✅ CRITICAL: Load your JavaScript file #}
|
|
||||||
<script src="{{ url_for('static', path='admin/js/your-page.js') }}"></script>
|
|
||||||
{% 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! ✅
|
|
||||||
235
docs/getting-started/DATABASE_QUICK_REFERENCE.md
Normal file
235
docs/getting-started/DATABASE_QUICK_REFERENCE.md
Normal file
@@ -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 <repo>
|
||||||
|
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/<id>_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 <revision_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⬇️ Rolling Back
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Downgrade one version
|
||||||
|
alembic downgrade -1
|
||||||
|
|
||||||
|
# Downgrade to specific revision
|
||||||
|
alembic downgrade <revision_id>
|
||||||
|
|
||||||
|
# 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/<id>_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
|
||||||
883
docs/getting-started/DATABASE_SETUP_GUIDE.md
Normal file
883
docs/getting-started/DATABASE_SETUP_GUIDE.md
Normal file
@@ -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 <repository-url>
|
||||||
|
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:
|
||||||
|
# <revision_id> (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/<revision>_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 <revision_id>
|
||||||
|
|
||||||
|
# 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.
|
||||||
@@ -1,29 +1,38 @@
|
|||||||
# models/database/__init__.py
|
# models/database/__init__.py
|
||||||
"""Database models package."""
|
"""Database models package."""
|
||||||
|
from .admin import AdminAuditLog, AdminNotification, AdminSetting, PlatformAlert, AdminSession
|
||||||
from .base import Base
|
from .base import Base
|
||||||
from .customer import Customer
|
from .customer import Customer, CustomerAddress
|
||||||
from .order import Order
|
from .order import Order, OrderItem
|
||||||
from .user import User
|
from .user import User
|
||||||
from .marketplace_product import MarketplaceProduct
|
from .marketplace_product import MarketplaceProduct
|
||||||
from .inventory import Inventory
|
from .inventory import Inventory
|
||||||
from .vendor import Vendor
|
from .vendor import Vendor, Role, VendorUser
|
||||||
from .vendor_domain import VendorDomain
|
from .vendor_domain import VendorDomain
|
||||||
from .vendor_theme import VendorTheme
|
from .vendor_theme import VendorTheme
|
||||||
from .product import Product
|
from .product import Product
|
||||||
from .marketplace_import_job import MarketplaceImportJob
|
from .marketplace_import_job import MarketplaceImportJob
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
# Admin-specific models
|
||||||
|
"AdminAuditLog",
|
||||||
|
"AdminNotification",
|
||||||
|
"AdminSetting",
|
||||||
|
"PlatformAlert",
|
||||||
|
"AdminSession",
|
||||||
"Base",
|
"Base",
|
||||||
"User",
|
"User",
|
||||||
"MarketplaceProduct",
|
|
||||||
"Inventory",
|
"Inventory",
|
||||||
"Customer",
|
"Customer",
|
||||||
|
"CustomerAddress",
|
||||||
"Order",
|
"Order",
|
||||||
|
"OrderItem",
|
||||||
"Vendor",
|
"Vendor",
|
||||||
|
"VendorUser",
|
||||||
|
"Role",
|
||||||
"Product",
|
"Product",
|
||||||
"MarketplaceImportJob",
|
"MarketplaceImportJob",
|
||||||
|
"MarketplaceProduct",
|
||||||
"VendorDomain",
|
"VendorDomain",
|
||||||
"VendorTheme"
|
"VendorTheme"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
319
scripts/seed_database.py
Normal file
319
scripts/seed_database.py
Normal file
@@ -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)
|
||||||
@@ -1,22 +1,17 @@
|
|||||||
// static/admin/js/vendor-theme.js
|
// static/admin/js/vendor-theme.js (FIXED VERSION)
|
||||||
/**
|
/**
|
||||||
* Vendor Theme Editor - Alpine.js Component
|
* Vendor Theme Editor - Alpine.js Component
|
||||||
* Manages theme customization for vendor shops
|
* 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
|
// Use the pre-configured theme logger from centralized log-config.js
|
||||||
|
const themeLog = window.LogConfig.loggers.vendorTheme;
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ALPINE.JS COMPONENT
|
// ALPINE.JS COMPONENT
|
||||||
@@ -50,254 +45,278 @@ function adminVendorTheme() {
|
|||||||
},
|
},
|
||||||
fonts: {
|
fonts: {
|
||||||
heading: 'Inter, sans-serif',
|
heading: 'Inter, sans-serif',
|
||||||
body: 'Inter, sans-serif'
|
body: 'Inter, sans-serif',
|
||||||
|
size_base: '16px',
|
||||||
|
size_heading: '2rem'
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
style: 'grid',
|
style: 'grid',
|
||||||
header: 'fixed',
|
header_position: 'fixed',
|
||||||
product_card: 'modern'
|
product_card_style: 'card',
|
||||||
|
sidebar_position: 'left'
|
||||||
},
|
},
|
||||||
branding: {
|
branding: {
|
||||||
logo: null,
|
logo_url: '',
|
||||||
logo_dark: null,
|
favicon_url: '',
|
||||||
favicon: null,
|
banner_url: ''
|
||||||
banner: null
|
|
||||||
},
|
},
|
||||||
custom_css: ''
|
custom_css: '',
|
||||||
|
social_links: {
|
||||||
|
facebook: '',
|
||||||
|
instagram: '',
|
||||||
|
twitter: '',
|
||||||
|
linkedin: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Available presets
|
// Available presets
|
||||||
presets: [],
|
presets: [],
|
||||||
|
selectedPreset: null,
|
||||||
|
|
||||||
// ============================================================================
|
// ====================================================================
|
||||||
// INITIALIZATION
|
// INITIALIZATION
|
||||||
// ============================================================================
|
// ====================================================================
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
themeLog.info('=== VENDOR THEME EDITOR INITIALIZING ===');
|
themeLog.info('Initializing vendor theme editor');
|
||||||
|
|
||||||
// ✅ CRITICAL: Prevent multiple initializations
|
// Start performance timer
|
||||||
if (window._vendorThemeInitialized) {
|
const startTime = performance.now();
|
||||||
themeLog.warn('Theme editor already initialized, skipping...');
|
|
||||||
return;
|
try {
|
||||||
|
// Extract vendor code from URL
|
||||||
|
const urlParts = window.location.pathname.split('/');
|
||||||
|
this.vendorCode = urlParts[urlParts.indexOf('vendors') + 1];
|
||||||
|
|
||||||
|
themeLog.debug('Vendor code from URL:', this.vendorCode);
|
||||||
|
|
||||||
|
if (!this.vendorCode) {
|
||||||
|
throw new Error('Vendor code not found in URL');
|
||||||
}
|
}
|
||||||
window._vendorThemeInitialized = true;
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
// Load data in parallel
|
||||||
|
themeLog.group('Loading theme data');
|
||||||
|
|
||||||
// Get vendor code from URL
|
|
||||||
this.vendorCode = this.getVendorCodeFromURL();
|
|
||||||
themeLog.info('Vendor code:', this.vendorCode);
|
|
||||||
|
|
||||||
// Load data
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadVendorData(),
|
this.loadVendor(),
|
||||||
this.loadTheme(),
|
this.loadTheme(),
|
||||||
this.loadPresets()
|
this.loadPresets()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const duration = Date.now() - startTime;
|
themeLog.groupEnd();
|
||||||
themeLog.info(`=== THEME EDITOR INITIALIZATION COMPLETE (${duration}ms) ===`);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============================================================================
|
// Log performance
|
||||||
// URL HELPERS
|
const duration = performance.now() - startTime;
|
||||||
// ============================================================================
|
window.LogConfig.logPerformance('Theme Editor Init', duration);
|
||||||
|
|
||||||
getVendorCodeFromURL() {
|
themeLog.info('Theme editor initialized successfully');
|
||||||
const pathParts = window.location.pathname.split('/');
|
|
||||||
const vendorIndex = pathParts.indexOf('vendors');
|
|
||||||
return pathParts[vendorIndex + 1];
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// DATA LOADING
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
async loadVendorData() {
|
|
||||||
themeLog.info('Loading vendor data...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const startTime = Date.now();
|
|
||||||
const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`);
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
this.vendor = response;
|
|
||||||
themeLog.info(`Vendor loaded in ${duration}ms:`, this.vendor.name);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
themeLog.error('Failed to load vendor:', error);
|
// Use centralized error logger
|
||||||
this.error = 'Failed to load vendor data';
|
window.LogConfig.logError(error, 'Theme Editor Init');
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
themeLog.error('Failed to load theme:', error);
|
|
||||||
this.error = 'Failed to load theme';
|
|
||||||
Utils.showToast('Failed to load theme', 'error');
|
|
||||||
|
|
||||||
|
this.error = error.message || 'Failed to initialize theme editor';
|
||||||
|
Utils.showToast(this.error, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
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 {
|
try {
|
||||||
const startTime = Date.now();
|
// ✅ FIX: apiClient returns data directly, not response.data
|
||||||
const response = await apiClient.get('/admin/vendor-themes/presets');
|
const response = await apiClient.get(url);
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
this.presets = response.presets || [];
|
// ✅ Direct assignment - response IS the data
|
||||||
themeLog.info(`${this.presets.length} presets loaded in ${duration}ms`);
|
this.vendor = response;
|
||||||
|
|
||||||
|
window.LogConfig.logApiCall('GET', url, this.vendor, 'response');
|
||||||
|
themeLog.debug('Vendor loaded:', this.vendor);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
themeLog.warn('Failed to load presets:', error);
|
themeLog.error('Failed to load vendor:', error);
|
||||||
// Non-critical error, continue without presets
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============================================================================
|
async loadTheme() {
|
||||||
// PRESET OPERATIONS
|
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) {
|
async applyPreset(presetName) {
|
||||||
themeLog.info(`Applying preset: ${presetName}`);
|
themeLog.info(`Applying preset: ${presetName}`);
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const url = `/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`;
|
||||||
const response = await apiClient.post(
|
window.LogConfig.logApiCall('POST', url, null, 'request');
|
||||||
`/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`
|
|
||||||
);
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
|
// ✅ 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) {
|
if (response && response.theme) {
|
||||||
// Update theme data with preset
|
|
||||||
this.themeData = {
|
this.themeData = {
|
||||||
theme_name: response.theme.theme_name,
|
...this.themeData,
|
||||||
colors: response.theme.colors || this.themeData.colors,
|
...response.theme
|
||||||
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 || ''
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Utils.showToast(`Applied ${presetName} preset successfully`, 'success');
|
|
||||||
themeLog.info(`Preset applied in ${duration}ms`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
themeLog.info(`Preset '${presetName}' applied successfully`);
|
||||||
themeLog.error('Failed to apply preset:', error);
|
Utils.showToast(`Applied ${presetName} preset`, 'success');
|
||||||
const message = error.response?.data?.detail || 'Failed to apply preset';
|
|
||||||
Utils.showToast(message, 'error');
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
window.LogConfig.logError(error, 'Apply Preset');
|
||||||
|
Utils.showToast('Failed to apply preset', 'error');
|
||||||
} finally {
|
} finally {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async resetToDefault() {
|
async resetTheme() {
|
||||||
if (!confirm('Are you sure you want to reset to default theme? This will discard all customizations.')) {
|
if (!confirm('Reset theme to default? This cannot be undone.')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
themeLog.info('Resetting to default theme');
|
themeLog.warn('Resetting theme to default');
|
||||||
await this.applyPreset('default');
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// SAVE OPERATIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
async saveTheme() {
|
|
||||||
themeLog.info('Saving theme:', this.themeData);
|
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
this.error = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const url = `/admin/vendor-themes/${this.vendorCode}`;
|
||||||
const response = await apiClient.put(
|
window.LogConfig.logApiCall('DELETE', url, null, 'request');
|
||||||
`/admin/vendor-themes/${this.vendorCode}`,
|
|
||||||
this.themeData
|
|
||||||
);
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
|
|
||||||
if (response) {
|
await apiClient.delete(url);
|
||||||
Utils.showToast('Theme saved successfully', 'success');
|
|
||||||
themeLog.info(`Theme saved in ${duration}ms`);
|
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) {
|
} catch (error) {
|
||||||
themeLog.error('Failed to save theme:', error);
|
window.LogConfig.logError(error, 'Reset Theme');
|
||||||
const message = error.response?.data?.detail || 'Failed to save theme';
|
Utils.showToast('Failed to reset theme', 'error');
|
||||||
Utils.showToast(message, 'error');
|
|
||||||
this.error = message;
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============================================================================
|
// ====================================================================
|
||||||
// HELPER METHODS
|
// UTILITY METHODS
|
||||||
// ============================================================================
|
// ====================================================================
|
||||||
|
|
||||||
formatDate(dateString) {
|
previewTheme() {
|
||||||
if (!dateString) return '-';
|
themeLog.debug('Opening theme preview');
|
||||||
return Utils.formatDate(dateString);
|
const previewUrl = `/vendor/${this.vendor?.subdomain || this.vendorCode}`;
|
||||||
|
window.open(previewUrl, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
getPreviewStyle() {
|
updateColor(key, value) {
|
||||||
return {
|
themeLog.debug(`Color updated: ${key} = ${value}`);
|
||||||
'--color-primary': this.themeData.colors.primary,
|
this.themeData.colors[key] = value;
|
||||||
'--color-secondary': this.themeData.colors.secondary,
|
},
|
||||||
'--color-accent': this.themeData.colors.accent,
|
|
||||||
'--color-background': this.themeData.colors.background,
|
updateFont(type, value) {
|
||||||
'--color-text': this.themeData.colors.text,
|
themeLog.debug(`Font updated: ${type} = ${value}`);
|
||||||
'--color-border': this.themeData.colors.border,
|
this.themeData.fonts[type] = value;
|
||||||
'--font-heading': this.themeData.fonts.heading,
|
},
|
||||||
'--font-body': this.themeData.fonts.body,
|
|
||||||
};
|
updateLayout(key, value) {
|
||||||
|
themeLog.debug(`Layout updated: ${key} = ${value}`);
|
||||||
|
this.themeData.layout[key] = value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const Icons = {
|
|||||||
'folder-open': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"/></svg>`,
|
'folder-open': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"/></svg>`,
|
||||||
'download': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>`,
|
'download': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>`,
|
||||||
'upload': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>`,
|
'upload': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>`,
|
||||||
|
'save': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/></svg>`,
|
||||||
|
|
||||||
// Settings & Tools
|
// Settings & Tools
|
||||||
'cog': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>`,
|
'cog': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>`,
|
||||||
@@ -77,6 +78,10 @@ const Icons = {
|
|||||||
'moon': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>`,
|
'moon': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>`,
|
||||||
'sun': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>`,
|
'sun': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>`,
|
||||||
|
|
||||||
|
// Design & Theming
|
||||||
|
'palette': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>`,
|
||||||
|
'color-swatch': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/></svg>`,
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
'location-marker': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg>`,
|
'location-marker': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/></svg>`,
|
||||||
'globe': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
|
'globe': `<svg class="{{classes}}" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>`,
|
||||||
|
|||||||
490
static/shared/js/log-config.js
Normal file
490
static/shared/js/log-config.js
Normal file
@@ -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');
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
Reference in New Issue
Block a user