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
|
||||
|
||||
# Database Configuration
|
||||
# DATABASE_URL=postgresql://username:password@localhost:5432/ecommerce_db
|
||||
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
|
||||
# For development, you can use SQLite:
|
||||
DATABASE_URL=sqlite:///./ecommerce.db
|
||||
DATABASE_URL=sqlite:///./wizamart.db
|
||||
|
||||
# Documentation
|
||||
# .env.development
|
||||
@@ -49,5 +49,5 @@ SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
|
||||
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
||||
|
||||
# DNS verification
|
||||
DNS_VERIFICATION_PREFIX=_letzshop-verify
|
||||
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
||||
DNS_VERIFICATION_TTL=3600
|
||||
@@ -6,9 +6,9 @@ DESCRIPTION=Advanced product management system with JWT authentication
|
||||
VERSION=0.0.1
|
||||
|
||||
# Database Configuration
|
||||
# DATABASE_URL=postgresql://username:password@localhost:5432/ecommerce_db
|
||||
# DATABASE_URL=postgresql://username:password@localhost:5432/wizamart_db
|
||||
# For development, you can use SQLite:
|
||||
DATABASE_URL=sqlite:///./ecommerce.db
|
||||
DATABASE_URL=sqlite:///./wizamart.db
|
||||
|
||||
# Documentation
|
||||
# .env.development
|
||||
@@ -49,5 +49,5 @@ SSL_PROVIDER=letsencrypt # or "cloudflare", "manual"
|
||||
AUTO_PROVISION_SSL=False # Set to True if using automated SSL
|
||||
|
||||
# DNS verification
|
||||
DNS_VERIFICATION_PREFIX=_letzshop-verify
|
||||
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
||||
DNS_VERIFICATION_TTL=3600
|
||||
171
alembic/env.py
171
alembic/env.py
@@ -1,4 +1,15 @@
|
||||
# alembic/env.py
|
||||
"""
|
||||
Alembic migration environment configuration.
|
||||
|
||||
This file is responsible for:
|
||||
1. Importing ALL database models so Alembic can detect schema changes
|
||||
2. Configuring the database connection
|
||||
3. Running migrations in online/offline mode
|
||||
|
||||
CRITICAL: Every model in models/database/__init__.py must be imported here!
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from logging.config import fileConfig
|
||||
@@ -12,93 +23,160 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
||||
from app.core.config import settings
|
||||
from models.database.base import Base
|
||||
|
||||
# === IMPORTANT: Import all your DATABASE models here ===
|
||||
# Only import SQLAlchemy models, not Pydantic API models
|
||||
print("[ALEMBIC] Importing database models...")
|
||||
# ============================================================================
|
||||
# IMPORT ALL DATABASE MODELS
|
||||
# ============================================================================
|
||||
# CRITICAL: Every model must be imported here so Alembic can detect tables!
|
||||
# If a model is not imported, Alembic will not create/update its table.
|
||||
#
|
||||
# Models list must match: models/database/__init__.py
|
||||
# ============================================================================
|
||||
|
||||
# Core models
|
||||
print("[ALEMBIC] Importing database models...")
|
||||
print("=" * 70)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# ADMIN MODELS
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.admin import (
|
||||
AdminAuditLog,
|
||||
AdminNotification,
|
||||
AdminSetting,
|
||||
PlatformAlert,
|
||||
AdminSession
|
||||
)
|
||||
|
||||
print(" ✓ Admin models imported (5 models)")
|
||||
print(" - AdminAuditLog")
|
||||
print(" - AdminNotification")
|
||||
print(" - AdminSetting")
|
||||
print(" - PlatformAlert")
|
||||
print(" - AdminSession")
|
||||
except ImportError as e:
|
||||
print(f" ✗ Admin models failed: {e}")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# USER MODEL
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.user import User
|
||||
|
||||
print(" ✓ User model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ User model failed: {e}")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# VENDOR MODELS
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.vendor import Vendor, VendorUser, Role
|
||||
print(" ✓ Vendor models imported (Vendor, VendorUser, Role)")
|
||||
|
||||
print(" ✓ Vendor models imported (3 models)")
|
||||
print(" - Vendor")
|
||||
print(" - VendorUser")
|
||||
print(" - Role")
|
||||
except ImportError as e:
|
||||
print(f" ✗ Vendor models failed: {e}")
|
||||
|
||||
try:
|
||||
from models.database.vendor_domain import VendorDomain
|
||||
|
||||
print(" ✓ VendorDomain model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ VendorDomain model failed: {e}")
|
||||
|
||||
try:
|
||||
from models.database.vendor_theme import VendorTheme
|
||||
|
||||
print(" ✓ VendorTheme model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ VendorTheme model failed: {e}")
|
||||
|
||||
# Product models
|
||||
try:
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
print(" ✓ MarketplaceProduct model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ MarketplaceProduct model failed: {e}")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# PRODUCT MODELS
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.product import Product
|
||||
|
||||
print(" ✓ Product model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ Product model failed: {e}")
|
||||
|
||||
# Inventory
|
||||
try:
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
|
||||
print(" ✓ MarketplaceProduct model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ MarketplaceProduct model failed: {e}")
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# INVENTORY MODEL
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.inventory import Inventory
|
||||
|
||||
print(" ✓ Inventory model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ Inventory model failed: {e}")
|
||||
|
||||
# Marketplace imports
|
||||
# ----------------------------------------------------------------------------
|
||||
# MARKETPLACE IMPORT
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
print(" ✓ MarketplaceImportJob model imported")
|
||||
except ImportError as e:
|
||||
print(f" ✗ MarketplaceImportJob model failed: {e}")
|
||||
|
||||
# Customer models
|
||||
# ----------------------------------------------------------------------------
|
||||
# CUSTOMER MODELS
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.customer import Customer, CustomerAddress
|
||||
print(" ✓ Customer models imported (Customer, CustomerAddress)")
|
||||
|
||||
print(" ✓ Customer models imported (2 models)")
|
||||
print(" - Customer")
|
||||
print(" - CustomerAddress")
|
||||
except ImportError as e:
|
||||
print(f" ✗ Customer models failed: {e}")
|
||||
|
||||
# Order models
|
||||
# ----------------------------------------------------------------------------
|
||||
# ORDER MODELS
|
||||
# ----------------------------------------------------------------------------
|
||||
try:
|
||||
from models.database.order import Order, OrderItem
|
||||
print(" ✓ Order models imported (Order, OrderItem)")
|
||||
|
||||
print(" ✓ Order models imported (2 models)")
|
||||
print(" - Order")
|
||||
print(" - OrderItem")
|
||||
except ImportError as e:
|
||||
print(f" ✗ Order models failed: {e}")
|
||||
|
||||
# Payment models (if you have them)
|
||||
try:
|
||||
from models.database.payment import Payment
|
||||
print(" ✓ Payment model imported")
|
||||
except ImportError as e:
|
||||
print(f" - Payment model not found (optional)")
|
||||
# ============================================================================
|
||||
# SUMMARY
|
||||
# ============================================================================
|
||||
print("=" * 70)
|
||||
print(f"[ALEMBIC] Model import completed")
|
||||
print(f"[ALEMBIC] Tables detected in metadata:")
|
||||
print("=" * 70)
|
||||
|
||||
# Shipping models (if you have them)
|
||||
try:
|
||||
from models.database.shipping import ShippingAddress
|
||||
print(" ✓ Shipping model imported")
|
||||
except ImportError as e:
|
||||
print(f" - Shipping model not found (optional)")
|
||||
if Base.metadata.tables:
|
||||
for i, table_name in enumerate(sorted(Base.metadata.tables.keys()), 1):
|
||||
print(f" {i:2d}. {table_name}")
|
||||
print("=" * 70)
|
||||
print(f"[ALEMBIC] Total tables: {len(Base.metadata.tables)}")
|
||||
else:
|
||||
print(" ⚠️ WARNING: No tables found in metadata!")
|
||||
print(" This usually means models are not being imported correctly.")
|
||||
|
||||
print(f"\n[ALEMBIC] Model import completed.")
|
||||
print(f"[ALEMBIC] Tables found: {list(Base.metadata.tables.keys())}")
|
||||
print(f"[ALEMBIC] Total tables to create: {len(Base.metadata.tables)}\n")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# ============================================================================
|
||||
# ALEMBIC CONFIGURATION
|
||||
# ============================================================================
|
||||
|
||||
# Alembic Config object
|
||||
config = context.config
|
||||
@@ -109,11 +187,20 @@ config.set_main_option("sqlalchemy.url", settings.database_url)
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# Set target metadata from Base
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode."""
|
||||
"""
|
||||
Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL and not an Engine,
|
||||
though an Engine is acceptable here as well. By skipping the Engine
|
||||
creation we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the script output.
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
@@ -127,7 +214,12 @@ def run_migrations_offline() -> None:
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode."""
|
||||
"""
|
||||
Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine and associate a connection
|
||||
with the context.
|
||||
"""
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix="sqlalchemy.",
|
||||
@@ -135,12 +227,19 @@ def run_migrations_online() -> None:
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MAIN EXECUTION
|
||||
# ============================================================================
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""initial_schema_with_proper_relationships
|
||||
"""Initial migration - all tables
|
||||
|
||||
Revision ID: 6fe45d3d84c4
|
||||
Revision ID: 4951b2e50581
|
||||
Revises:
|
||||
Create Date: 2025-10-07 22:11:56.036486
|
||||
Create Date: 2025-10-27 22:28:33.137564
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
@@ -12,7 +12,7 @@ import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '6fe45d3d84c4'
|
||||
revision: str = '4951b2e50581'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
@@ -92,6 +92,112 @@ def upgrade() -> None:
|
||||
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
||||
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
||||
op.create_table('admin_audit_logs',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('admin_user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('action', sa.String(length=100), nullable=False),
|
||||
sa.Column('target_type', sa.String(length=50), nullable=False),
|
||||
sa.Column('target_id', sa.String(length=100), nullable=False),
|
||||
sa.Column('details', sa.JSON(), nullable=True),
|
||||
sa.Column('ip_address', sa.String(length=45), nullable=True),
|
||||
sa.Column('user_agent', sa.Text(), nullable=True),
|
||||
sa.Column('request_id', sa.String(length=100), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['admin_user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_admin_audit_logs_action'), 'admin_audit_logs', ['action'], unique=False)
|
||||
op.create_index(op.f('ix_admin_audit_logs_admin_user_id'), 'admin_audit_logs', ['admin_user_id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_audit_logs_id'), 'admin_audit_logs', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_audit_logs_target_id'), 'admin_audit_logs', ['target_id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_audit_logs_target_type'), 'admin_audit_logs', ['target_type'], unique=False)
|
||||
op.create_table('admin_notifications',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('type', sa.String(length=50), nullable=False),
|
||||
sa.Column('priority', sa.String(length=20), nullable=True),
|
||||
sa.Column('title', sa.String(length=200), nullable=False),
|
||||
sa.Column('message', sa.Text(), nullable=False),
|
||||
sa.Column('is_read', sa.Boolean(), nullable=True),
|
||||
sa.Column('read_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('read_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('action_required', sa.Boolean(), nullable=True),
|
||||
sa.Column('action_url', sa.String(length=500), nullable=True),
|
||||
sa.Column('notification_metadata', sa.JSON(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['read_by_user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_admin_notifications_action_required'), 'admin_notifications', ['action_required'], unique=False)
|
||||
op.create_index(op.f('ix_admin_notifications_id'), 'admin_notifications', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_notifications_is_read'), 'admin_notifications', ['is_read'], unique=False)
|
||||
op.create_index(op.f('ix_admin_notifications_priority'), 'admin_notifications', ['priority'], unique=False)
|
||||
op.create_index(op.f('ix_admin_notifications_type'), 'admin_notifications', ['type'], unique=False)
|
||||
op.create_table('admin_sessions',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('admin_user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('session_token', sa.String(length=255), nullable=False),
|
||||
sa.Column('ip_address', sa.String(length=45), nullable=False),
|
||||
sa.Column('user_agent', sa.Text(), nullable=True),
|
||||
sa.Column('login_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('last_activity_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('logout_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('logout_reason', sa.String(length=50), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['admin_user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_admin_sessions_admin_user_id'), 'admin_sessions', ['admin_user_id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_sessions_id'), 'admin_sessions', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_sessions_is_active'), 'admin_sessions', ['is_active'], unique=False)
|
||||
op.create_index(op.f('ix_admin_sessions_login_at'), 'admin_sessions', ['login_at'], unique=False)
|
||||
op.create_index(op.f('ix_admin_sessions_session_token'), 'admin_sessions', ['session_token'], unique=True)
|
||||
op.create_table('admin_settings',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(length=100), nullable=False),
|
||||
sa.Column('value', sa.Text(), nullable=False),
|
||||
sa.Column('value_type', sa.String(length=20), nullable=True),
|
||||
sa.Column('category', sa.String(length=50), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('is_encrypted', sa.Boolean(), nullable=True),
|
||||
sa.Column('is_public', sa.Boolean(), nullable=True),
|
||||
sa.Column('last_modified_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['last_modified_by_user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_admin_settings_category'), 'admin_settings', ['category'], unique=False)
|
||||
op.create_index(op.f('ix_admin_settings_id'), 'admin_settings', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_admin_settings_key'), 'admin_settings', ['key'], unique=True)
|
||||
op.create_table('platform_alerts',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('alert_type', sa.String(length=50), nullable=False),
|
||||
sa.Column('severity', sa.String(length=20), nullable=False),
|
||||
sa.Column('title', sa.String(length=200), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('affected_vendors', sa.JSON(), nullable=True),
|
||||
sa.Column('affected_systems', sa.JSON(), nullable=True),
|
||||
sa.Column('is_resolved', sa.Boolean(), nullable=True),
|
||||
sa.Column('resolved_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('resolved_by_user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('resolution_notes', sa.Text(), nullable=True),
|
||||
sa.Column('auto_generated', sa.Boolean(), nullable=True),
|
||||
sa.Column('occurrence_count', sa.Integer(), nullable=True),
|
||||
sa.Column('first_occurred_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('last_occurred_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['resolved_by_user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_platform_alerts_alert_type'), 'platform_alerts', ['alert_type'], unique=False)
|
||||
op.create_index(op.f('ix_platform_alerts_id'), 'platform_alerts', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_platform_alerts_is_resolved'), 'platform_alerts', ['is_resolved'], unique=False)
|
||||
op.create_index(op.f('ix_platform_alerts_severity'), 'platform_alerts', ['severity'], unique=False)
|
||||
op.create_table('vendors',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_code', sa.String(), nullable=False),
|
||||
@@ -99,7 +205,6 @@ def upgrade() -> None:
|
||||
sa.Column('name', sa.String(), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('owner_user_id', sa.Integer(), nullable=False),
|
||||
sa.Column('theme_config', sa.JSON(), nullable=True),
|
||||
sa.Column('contact_email', sa.String(), nullable=True),
|
||||
sa.Column('contact_phone', sa.String(), nullable=True),
|
||||
sa.Column('website', sa.String(), nullable=True),
|
||||
@@ -203,6 +308,54 @@ def upgrade() -> None:
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_roles_id'), 'roles', ['id'], unique=False)
|
||||
op.create_table('vendor_domains',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_id', sa.Integer(), nullable=False),
|
||||
sa.Column('domain', sa.String(length=255), nullable=False),
|
||||
sa.Column('is_primary', sa.Boolean(), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||
sa.Column('ssl_status', sa.String(length=50), nullable=True),
|
||||
sa.Column('ssl_verified_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('verification_token', sa.String(length=100), nullable=True),
|
||||
sa.Column('is_verified', sa.Boolean(), nullable=False),
|
||||
sa.Column('verified_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('vendor_id', 'domain', name='uq_vendor_domain'),
|
||||
sa.UniqueConstraint('verification_token')
|
||||
)
|
||||
op.create_index('idx_domain_active', 'vendor_domains', ['domain', 'is_active'], unique=False)
|
||||
op.create_index('idx_vendor_primary', 'vendor_domains', ['vendor_id', 'is_primary'], unique=False)
|
||||
op.create_index(op.f('ix_vendor_domains_domain'), 'vendor_domains', ['domain'], unique=True)
|
||||
op.create_index(op.f('ix_vendor_domains_id'), 'vendor_domains', ['id'], unique=False)
|
||||
op.create_table('vendor_themes',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_id', sa.Integer(), nullable=False),
|
||||
sa.Column('theme_name', sa.String(length=100), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('colors', sa.JSON(), nullable=True),
|
||||
sa.Column('font_family_heading', sa.String(length=100), nullable=True),
|
||||
sa.Column('font_family_body', sa.String(length=100), nullable=True),
|
||||
sa.Column('logo_url', sa.String(length=500), nullable=True),
|
||||
sa.Column('logo_dark_url', sa.String(length=500), nullable=True),
|
||||
sa.Column('favicon_url', sa.String(length=500), nullable=True),
|
||||
sa.Column('banner_url', sa.String(length=500), nullable=True),
|
||||
sa.Column('layout_style', sa.String(length=50), nullable=True),
|
||||
sa.Column('header_style', sa.String(length=50), nullable=True),
|
||||
sa.Column('product_card_style', sa.String(length=50), nullable=True),
|
||||
sa.Column('custom_css', sa.Text(), nullable=True),
|
||||
sa.Column('social_links', sa.JSON(), nullable=True),
|
||||
sa.Column('meta_title_template', sa.String(length=200), nullable=True),
|
||||
sa.Column('meta_description', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('vendor_id')
|
||||
)
|
||||
op.create_index(op.f('ix_vendor_themes_id'), 'vendor_themes', ['id'], unique=False)
|
||||
op.create_table('customer_addresses',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_id', sa.Integer(), nullable=False),
|
||||
@@ -342,6 +495,13 @@ def downgrade() -> None:
|
||||
op.drop_table('inventory')
|
||||
op.drop_index(op.f('ix_customer_addresses_id'), table_name='customer_addresses')
|
||||
op.drop_table('customer_addresses')
|
||||
op.drop_index(op.f('ix_vendor_themes_id'), table_name='vendor_themes')
|
||||
op.drop_table('vendor_themes')
|
||||
op.drop_index(op.f('ix_vendor_domains_id'), table_name='vendor_domains')
|
||||
op.drop_index(op.f('ix_vendor_domains_domain'), table_name='vendor_domains')
|
||||
op.drop_index('idx_vendor_primary', table_name='vendor_domains')
|
||||
op.drop_index('idx_domain_active', table_name='vendor_domains')
|
||||
op.drop_table('vendor_domains')
|
||||
op.drop_index(op.f('ix_roles_id'), table_name='roles')
|
||||
op.drop_table('roles')
|
||||
op.drop_index(op.f('ix_products_id'), table_name='products')
|
||||
@@ -363,6 +523,33 @@ def downgrade() -> None:
|
||||
op.drop_index(op.f('ix_vendors_subdomain'), table_name='vendors')
|
||||
op.drop_index(op.f('ix_vendors_id'), table_name='vendors')
|
||||
op.drop_table('vendors')
|
||||
op.drop_index(op.f('ix_platform_alerts_severity'), table_name='platform_alerts')
|
||||
op.drop_index(op.f('ix_platform_alerts_is_resolved'), table_name='platform_alerts')
|
||||
op.drop_index(op.f('ix_platform_alerts_id'), table_name='platform_alerts')
|
||||
op.drop_index(op.f('ix_platform_alerts_alert_type'), table_name='platform_alerts')
|
||||
op.drop_table('platform_alerts')
|
||||
op.drop_index(op.f('ix_admin_settings_key'), table_name='admin_settings')
|
||||
op.drop_index(op.f('ix_admin_settings_id'), table_name='admin_settings')
|
||||
op.drop_index(op.f('ix_admin_settings_category'), table_name='admin_settings')
|
||||
op.drop_table('admin_settings')
|
||||
op.drop_index(op.f('ix_admin_sessions_session_token'), table_name='admin_sessions')
|
||||
op.drop_index(op.f('ix_admin_sessions_login_at'), table_name='admin_sessions')
|
||||
op.drop_index(op.f('ix_admin_sessions_is_active'), table_name='admin_sessions')
|
||||
op.drop_index(op.f('ix_admin_sessions_id'), table_name='admin_sessions')
|
||||
op.drop_index(op.f('ix_admin_sessions_admin_user_id'), table_name='admin_sessions')
|
||||
op.drop_table('admin_sessions')
|
||||
op.drop_index(op.f('ix_admin_notifications_type'), table_name='admin_notifications')
|
||||
op.drop_index(op.f('ix_admin_notifications_priority'), table_name='admin_notifications')
|
||||
op.drop_index(op.f('ix_admin_notifications_is_read'), table_name='admin_notifications')
|
||||
op.drop_index(op.f('ix_admin_notifications_id'), table_name='admin_notifications')
|
||||
op.drop_index(op.f('ix_admin_notifications_action_required'), table_name='admin_notifications')
|
||||
op.drop_table('admin_notifications')
|
||||
op.drop_index(op.f('ix_admin_audit_logs_target_type'), table_name='admin_audit_logs')
|
||||
op.drop_index(op.f('ix_admin_audit_logs_target_id'), table_name='admin_audit_logs')
|
||||
op.drop_index(op.f('ix_admin_audit_logs_id'), table_name='admin_audit_logs')
|
||||
op.drop_index(op.f('ix_admin_audit_logs_admin_user_id'), table_name='admin_audit_logs')
|
||||
op.drop_index(op.f('ix_admin_audit_logs_action'), table_name='admin_audit_logs')
|
||||
op.drop_table('admin_audit_logs')
|
||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_id'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||
@@ -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).
|
||||
|
||||
**Verification Process:**
|
||||
1. Queries DNS for TXT record: `_letzshop-verify.{domain}`
|
||||
1. Queries DNS for TXT record: `_wizamart-verify.{domain}`
|
||||
2. Checks if verification token matches
|
||||
3. If found, marks domain as verified
|
||||
|
||||
**Requirements:**
|
||||
- Vendor must have added TXT record to their DNS
|
||||
- DNS propagation may take 5-15 minutes
|
||||
- Record format: `_letzshop-verify.domain.com` TXT `{token}`
|
||||
- Record format: `_wizamart-verify.domain.com` TXT `{token}`
|
||||
|
||||
**After verification:**
|
||||
- Domain can be activated
|
||||
|
||||
@@ -73,7 +73,7 @@ class Settings(BaseSettings):
|
||||
auto_provision_ssl: bool = False # Set to True if using automated SSL
|
||||
|
||||
# DNS verification
|
||||
dns_verification_prefix: str = "_letzshop-verify"
|
||||
dns_verification_prefix: str = "_wizamart-verify"
|
||||
dns_verification_ttl: int = 3600
|
||||
|
||||
model_config = {"env_file": ".env"} # Updated syntax for Pydantic v2
|
||||
|
||||
@@ -286,7 +286,7 @@ class VendorDomainService:
|
||||
Verify domain ownership via DNS TXT record.
|
||||
|
||||
The vendor must add a TXT record:
|
||||
Name: _letzshop-verify.{domain}
|
||||
Name: _wizamart-verify.{domain}
|
||||
Value: {verification_token}
|
||||
|
||||
Args:
|
||||
@@ -313,7 +313,7 @@ class VendorDomainService:
|
||||
# Query DNS TXT records
|
||||
try:
|
||||
txt_records = dns.resolver.resolve(
|
||||
f"_letzshop-verify.{domain.domain}",
|
||||
f"_wizamart-verify.{domain.domain}",
|
||||
'TXT'
|
||||
)
|
||||
|
||||
@@ -339,7 +339,7 @@ class VendorDomainService:
|
||||
except dns.resolver.NXDOMAIN:
|
||||
raise DomainVerificationFailedException(
|
||||
domain.domain,
|
||||
f"DNS record _letzshop-verify.{domain.domain} not found"
|
||||
f"DNS record _wizamart-verify.{domain.domain} not found"
|
||||
)
|
||||
except dns.resolver.NoAnswer:
|
||||
raise DomainVerificationFailedException(
|
||||
@@ -394,7 +394,7 @@ class VendorDomainService:
|
||||
},
|
||||
"txt_record": {
|
||||
"type": "TXT",
|
||||
"name": "_letzshop-verify",
|
||||
"name": "_wizamart-verify",
|
||||
"value": domain.verification_token,
|
||||
"ttl": 3600
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<!-- Core Scripts - ORDER MATTERS! -->
|
||||
|
||||
<!-- 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) -->
|
||||
<script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
<!-- Typography Section -->
|
||||
<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">
|
||||
<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
|
||||
</h3>
|
||||
<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
|
||||
|
||||
Type: TXT
|
||||
Name: _letzshop-verify
|
||||
Name: _wizamart-verify
|
||||
Value: abc123xyz (verification token from your platform)
|
||||
TTL: 3600
|
||||
```
|
||||
@@ -161,7 +161,7 @@ To prevent domain hijacking, verify the vendor owns the domain:
|
||||
2. System generates verification token: `abc123xyz`
|
||||
3. Vendor adds DNS TXT record:
|
||||
```
|
||||
Name: _letzshop-verify.customdomain1.com
|
||||
Name: _wizamart-verify.customdomain1.com
|
||||
Type: TXT
|
||||
Value: abc123xyz
|
||||
```
|
||||
@@ -177,7 +177,7 @@ def verify_domain(domain_id: int, db: Session):
|
||||
|
||||
# Query DNS for TXT record
|
||||
txt_records = dns.resolver.resolve(
|
||||
f"_letzshop-verify.{domain.domain}",
|
||||
f"_wizamart-verify.{domain.domain}",
|
||||
'TXT'
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ Name: @
|
||||
Value: 123.45.67.89 (your server IP)
|
||||
|
||||
Type: TXT
|
||||
Name: _letzshop-verify
|
||||
Name: _wizamart-verify
|
||||
Value: abc123xyz
|
||||
```
|
||||
|
||||
@@ -125,7 +125,7 @@ pip install dnspython
|
||||
import dns.resolver
|
||||
|
||||
# Test querying TXT record
|
||||
answers = dns.resolver.resolve("_letzshop-verify.example.com", "TXT")
|
||||
answers = dns.resolver.resolve("_wizamart-verify.example.com", "TXT")
|
||||
for txt in answers:
|
||||
print(txt.to_text())
|
||||
```
|
||||
@@ -259,7 +259,7 @@ TTL: 3600
|
||||
**Verification TXT Record:**
|
||||
```
|
||||
Type: TXT
|
||||
Name: _letzshop-verify
|
||||
Name: _wizamart-verify
|
||||
Value: [token from step 9.1]
|
||||
TTL: 3600
|
||||
```
|
||||
@@ -351,7 +351,7 @@ HAVING COUNT(*) > 1;
|
||||
**Check DNS propagation:**
|
||||
```bash
|
||||
# Check if TXT record exists
|
||||
dig _letzshop-verify.customdomain1.com TXT
|
||||
dig _wizamart-verify.customdomain1.com TXT
|
||||
|
||||
# Should show verification token
|
||||
```
|
||||
@@ -203,7 +203,7 @@ When a vendor wants to use `customdomain1.com`:
|
||||
Value: 123.45.67.89 (your server IP)
|
||||
|
||||
2. Verification TXT Record:
|
||||
Name: _letzshop-verify
|
||||
Name: _wizamart-verify
|
||||
Value: [token from your platform]
|
||||
|
||||
3. Wait 5-15 minutes for DNS propagation
|
||||
@@ -331,7 +331,7 @@ Step 2: Get Instructions
|
||||
┌────────────────────────────────────┐
|
||||
│ System returns instructions: │
|
||||
│ "Add TXT record: │
|
||||
│ _letzshop-verify.myshop.com │
|
||||
│ _wizamart-verify.myshop.com │
|
||||
│ Value: abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
@@ -343,7 +343,7 @@ Step 3: Vendor Adds DNS Record
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ DNS Provider (GoDaddy/etc) │
|
||||
│ _letzshop-verify.myshop.com TXT │
|
||||
│ _wizamart-verify.myshop.com TXT │
|
||||
│ "abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
@@ -356,14 +356,14 @@ GET /api/v1/admin/vendors/domains/1/verification-instructions
|
||||
},
|
||||
"txt_record": {
|
||||
"type": "TXT",
|
||||
"name": "_letzshop-verify",
|
||||
"name": "_wizamart-verify",
|
||||
"value": "abc123xyz...",
|
||||
"ttl": 3600
|
||||
}
|
||||
}
|
||||
|
||||
# Step 2: Vendor adds DNS record
|
||||
# _letzshop-verify.myshop.com TXT "abc123xyz..."
|
||||
# _wizamart-verify.myshop.com TXT "abc123xyz..."
|
||||
|
||||
# Step 3: Verify domain
|
||||
POST /api/v1/admin/vendors/domains/1/verify
|
||||
@@ -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
|
||||
"""Database models package."""
|
||||
|
||||
from .admin import AdminAuditLog, AdminNotification, AdminSetting, PlatformAlert, AdminSession
|
||||
from .base import Base
|
||||
from .customer import Customer
|
||||
from .order import Order
|
||||
from .customer import Customer, CustomerAddress
|
||||
from .order import Order, OrderItem
|
||||
from .user import User
|
||||
from .marketplace_product import MarketplaceProduct
|
||||
from .inventory import Inventory
|
||||
from .vendor import Vendor
|
||||
from .vendor import Vendor, Role, VendorUser
|
||||
from .vendor_domain import VendorDomain
|
||||
from .vendor_theme import VendorTheme
|
||||
from .product import Product
|
||||
from .marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
__all__ = [
|
||||
# Admin-specific models
|
||||
"AdminAuditLog",
|
||||
"AdminNotification",
|
||||
"AdminSetting",
|
||||
"PlatformAlert",
|
||||
"AdminSession",
|
||||
"Base",
|
||||
"User",
|
||||
"MarketplaceProduct",
|
||||
"Inventory",
|
||||
"Customer",
|
||||
"CustomerAddress",
|
||||
"Order",
|
||||
"OrderItem",
|
||||
"Vendor",
|
||||
"VendorUser",
|
||||
"Role",
|
||||
"Product",
|
||||
"MarketplaceImportJob",
|
||||
"MarketplaceProduct",
|
||||
"VendorDomain",
|
||||
"VendorTheme"
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
* Manages theme customization for vendor shops
|
||||
*
|
||||
* REQUIRES: log-config.js to be loaded first
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// LOGGING CONFIGURATION
|
||||
// LOGGING CONFIGURATION (using centralized logger)
|
||||
// ============================================================================
|
||||
|
||||
const THEME_LOG_LEVEL = 3; // 1=error, 2=warn, 3=info, 4=debug
|
||||
|
||||
const themeLog = {
|
||||
error: (...args) => THEME_LOG_LEVEL >= 1 && console.error('❌ [THEME ERROR]', ...args),
|
||||
warn: (...args) => THEME_LOG_LEVEL >= 2 && console.warn('⚠️ [THEME WARN]', ...args),
|
||||
info: (...args) => THEME_LOG_LEVEL >= 3 && console.info('ℹ️ [THEME INFO]', ...args),
|
||||
debug: (...args) => THEME_LOG_LEVEL >= 4 && console.log('🔍 [THEME DEBUG]', ...args)
|
||||
};
|
||||
|
||||
// Use the pre-configured theme logger from centralized log-config.js
|
||||
const themeLog = window.LogConfig.loggers.vendorTheme;
|
||||
|
||||
// ============================================================================
|
||||
// ALPINE.JS COMPONENT
|
||||
@@ -50,254 +45,278 @@ function adminVendorTheme() {
|
||||
},
|
||||
fonts: {
|
||||
heading: 'Inter, sans-serif',
|
||||
body: 'Inter, sans-serif'
|
||||
body: 'Inter, sans-serif',
|
||||
size_base: '16px',
|
||||
size_heading: '2rem'
|
||||
},
|
||||
layout: {
|
||||
style: 'grid',
|
||||
header: 'fixed',
|
||||
product_card: 'modern'
|
||||
header_position: 'fixed',
|
||||
product_card_style: 'card',
|
||||
sidebar_position: 'left'
|
||||
},
|
||||
branding: {
|
||||
logo: null,
|
||||
logo_dark: null,
|
||||
favicon: null,
|
||||
banner: null
|
||||
logo_url: '',
|
||||
favicon_url: '',
|
||||
banner_url: ''
|
||||
},
|
||||
custom_css: ''
|
||||
custom_css: '',
|
||||
social_links: {
|
||||
facebook: '',
|
||||
instagram: '',
|
||||
twitter: '',
|
||||
linkedin: ''
|
||||
}
|
||||
},
|
||||
|
||||
// Available presets
|
||||
presets: [],
|
||||
selectedPreset: null,
|
||||
|
||||
// ============================================================================
|
||||
// ====================================================================
|
||||
// INITIALIZATION
|
||||
// ============================================================================
|
||||
// ====================================================================
|
||||
|
||||
async init() {
|
||||
themeLog.info('=== VENDOR THEME EDITOR INITIALIZING ===');
|
||||
themeLog.info('Initializing vendor theme editor');
|
||||
|
||||
// ✅ CRITICAL: Prevent multiple initializations
|
||||
if (window._vendorThemeInitialized) {
|
||||
themeLog.warn('Theme editor already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._vendorThemeInitialized = true;
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// Get vendor code from URL
|
||||
this.vendorCode = this.getVendorCodeFromURL();
|
||||
themeLog.info('Vendor code:', this.vendorCode);
|
||||
|
||||
// Load data
|
||||
await Promise.all([
|
||||
this.loadVendorData(),
|
||||
this.loadTheme(),
|
||||
this.loadPresets()
|
||||
]);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
themeLog.info(`=== THEME EDITOR INITIALIZATION COMPLETE (${duration}ms) ===`);
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// URL HELPERS
|
||||
// ============================================================================
|
||||
|
||||
getVendorCodeFromURL() {
|
||||
const pathParts = window.location.pathname.split('/');
|
||||
const vendorIndex = pathParts.indexOf('vendors');
|
||||
return pathParts[vendorIndex + 1];
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// DATA LOADING
|
||||
// ============================================================================
|
||||
|
||||
async loadVendorData() {
|
||||
themeLog.info('Loading vendor data...');
|
||||
// Start performance timer
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`);
|
||||
const duration = Date.now() - startTime;
|
||||
// Extract vendor code from URL
|
||||
const urlParts = window.location.pathname.split('/');
|
||||
this.vendorCode = urlParts[urlParts.indexOf('vendors') + 1];
|
||||
|
||||
this.vendor = response;
|
||||
themeLog.info(`Vendor loaded in ${duration}ms:`, this.vendor.name);
|
||||
themeLog.debug('Vendor code from URL:', this.vendorCode);
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to load vendor:', error);
|
||||
this.error = 'Failed to load vendor data';
|
||||
Utils.showToast('Failed to load vendor data', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
async loadTheme() {
|
||||
themeLog.info('Loading theme...');
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.get(`/admin/vendor-themes/${this.vendorCode}`);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (response) {
|
||||
// Merge loaded theme with defaults
|
||||
this.themeData = {
|
||||
theme_name: response.theme_name || 'default',
|
||||
colors: {
|
||||
...this.themeData.colors,
|
||||
...(response.colors || {})
|
||||
},
|
||||
fonts: {
|
||||
heading: response.fonts?.heading || this.themeData.fonts.heading,
|
||||
body: response.fonts?.body || this.themeData.fonts.body
|
||||
},
|
||||
layout: {
|
||||
style: response.layout?.style || this.themeData.layout.style,
|
||||
header: response.layout?.header || this.themeData.layout.header,
|
||||
product_card: response.layout?.product_card || this.themeData.layout.product_card
|
||||
},
|
||||
branding: {
|
||||
...this.themeData.branding,
|
||||
...(response.branding || {})
|
||||
},
|
||||
custom_css: response.custom_css || ''
|
||||
};
|
||||
|
||||
themeLog.info(`Theme loaded in ${duration}ms:`, this.themeData.theme_name);
|
||||
if (!this.vendorCode) {
|
||||
throw new Error('Vendor code not found in URL');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to load theme:', error);
|
||||
this.error = 'Failed to load theme';
|
||||
Utils.showToast('Failed to load theme', 'error');
|
||||
// Load data in parallel
|
||||
themeLog.group('Loading theme data');
|
||||
|
||||
await Promise.all([
|
||||
this.loadVendor(),
|
||||
this.loadTheme(),
|
||||
this.loadPresets()
|
||||
]);
|
||||
|
||||
themeLog.groupEnd();
|
||||
|
||||
// Log performance
|
||||
const duration = performance.now() - startTime;
|
||||
window.LogConfig.logPerformance('Theme Editor Init', duration);
|
||||
|
||||
themeLog.info('Theme editor initialized successfully');
|
||||
|
||||
} catch (error) {
|
||||
// Use centralized error logger
|
||||
window.LogConfig.logError(error, 'Theme Editor Init');
|
||||
|
||||
this.error = error.message || 'Failed to initialize theme editor';
|
||||
Utils.showToast(this.error, 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadPresets() {
|
||||
themeLog.info('Loading presets...');
|
||||
// ====================================================================
|
||||
// DATA LOADING
|
||||
// ====================================================================
|
||||
|
||||
async loadVendor() {
|
||||
themeLog.info('Loading vendor data');
|
||||
|
||||
const url = `/admin/vendors/${this.vendorCode}`;
|
||||
window.LogConfig.logApiCall('GET', url, null, 'request');
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.get('/admin/vendor-themes/presets');
|
||||
const duration = Date.now() - startTime;
|
||||
// ✅ FIX: apiClient returns data directly, not response.data
|
||||
const response = await apiClient.get(url);
|
||||
|
||||
this.presets = response.presets || [];
|
||||
themeLog.info(`${this.presets.length} presets loaded in ${duration}ms`);
|
||||
// ✅ Direct assignment - response IS the data
|
||||
this.vendor = response;
|
||||
|
||||
window.LogConfig.logApiCall('GET', url, this.vendor, 'response');
|
||||
themeLog.debug('Vendor loaded:', this.vendor);
|
||||
|
||||
} catch (error) {
|
||||
themeLog.warn('Failed to load presets:', error);
|
||||
// Non-critical error, continue without presets
|
||||
themeLog.error('Failed to load vendor:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// PRESET OPERATIONS
|
||||
// ============================================================================
|
||||
async loadTheme() {
|
||||
themeLog.info('Loading theme data');
|
||||
|
||||
const url = `/admin/vendor-themes/${this.vendorCode}`;
|
||||
window.LogConfig.logApiCall('GET', url, null, 'request');
|
||||
|
||||
try {
|
||||
// ✅ FIX: apiClient returns data directly
|
||||
const response = await apiClient.get(url);
|
||||
|
||||
// Merge with default theme data
|
||||
this.themeData = {
|
||||
...this.themeData,
|
||||
...response
|
||||
};
|
||||
|
||||
window.LogConfig.logApiCall('GET', url, this.themeData, 'response');
|
||||
themeLog.debug('Theme loaded:', this.themeData);
|
||||
|
||||
} catch (error) {
|
||||
themeLog.warn('Failed to load theme, using defaults:', error);
|
||||
// Continue with default theme
|
||||
}
|
||||
},
|
||||
|
||||
async loadPresets() {
|
||||
themeLog.info('Loading theme presets');
|
||||
|
||||
const url = '/admin/vendor-themes/presets';
|
||||
window.LogConfig.logApiCall('GET', url, null, 'request');
|
||||
|
||||
try {
|
||||
// ✅ FIX: apiClient returns data directly
|
||||
const response = await apiClient.get(url);
|
||||
|
||||
// ✅ Access presets directly from response, not response.data.presets
|
||||
this.presets = response.presets || [];
|
||||
|
||||
window.LogConfig.logApiCall('GET', url, response, 'response');
|
||||
themeLog.debug(`Loaded ${this.presets.length} presets`);
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to load presets:', error);
|
||||
this.presets = [];
|
||||
}
|
||||
},
|
||||
|
||||
// ====================================================================
|
||||
// THEME OPERATIONS
|
||||
// ====================================================================
|
||||
|
||||
async saveTheme() {
|
||||
if (this.saving) return;
|
||||
|
||||
themeLog.info('Saving theme changes');
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
const url = `/admin/vendor-themes/${this.vendorCode}`;
|
||||
window.LogConfig.logApiCall('PUT', url, this.themeData, 'request');
|
||||
|
||||
// ✅ FIX: apiClient returns data directly
|
||||
const response = await apiClient.put(url, this.themeData);
|
||||
|
||||
window.LogConfig.logApiCall('PUT', url, response, 'response');
|
||||
|
||||
const duration = performance.now() - startTime;
|
||||
window.LogConfig.logPerformance('Save Theme', duration);
|
||||
|
||||
themeLog.info('Theme saved successfully');
|
||||
Utils.showToast('Theme saved successfully', 'success');
|
||||
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Save Theme');
|
||||
this.error = 'Failed to save theme';
|
||||
Utils.showToast(this.error, 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
async applyPreset(presetName) {
|
||||
themeLog.info(`Applying preset: ${presetName}`);
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.post(
|
||||
`/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`
|
||||
);
|
||||
const duration = Date.now() - startTime;
|
||||
const url = `/admin/vendor-themes/${this.vendorCode}/preset/${presetName}`;
|
||||
window.LogConfig.logApiCall('POST', url, null, 'request');
|
||||
|
||||
// ✅ FIX: apiClient returns data directly
|
||||
const response = await apiClient.post(url);
|
||||
|
||||
window.LogConfig.logApiCall('POST', url, response, 'response');
|
||||
|
||||
// ✅ FIX: Access theme directly from response, not response.data.theme
|
||||
if (response && response.theme) {
|
||||
// Update theme data with preset
|
||||
this.themeData = {
|
||||
theme_name: response.theme.theme_name,
|
||||
colors: response.theme.colors || this.themeData.colors,
|
||||
fonts: response.theme.fonts || this.themeData.fonts,
|
||||
layout: response.theme.layout || this.themeData.layout,
|
||||
branding: response.theme.branding || this.themeData.branding,
|
||||
custom_css: response.theme.custom_css || ''
|
||||
...this.themeData,
|
||||
...response.theme
|
||||
};
|
||||
|
||||
Utils.showToast(`Applied ${presetName} preset successfully`, 'success');
|
||||
themeLog.info(`Preset applied in ${duration}ms`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to apply preset:', error);
|
||||
const message = error.response?.data?.detail || 'Failed to apply preset';
|
||||
Utils.showToast(message, 'error');
|
||||
themeLog.info(`Preset '${presetName}' applied successfully`);
|
||||
Utils.showToast(`Applied ${presetName} preset`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
window.LogConfig.logError(error, 'Apply Preset');
|
||||
Utils.showToast('Failed to apply preset', 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
async resetToDefault() {
|
||||
if (!confirm('Are you sure you want to reset to default theme? This will discard all customizations.')) {
|
||||
async resetTheme() {
|
||||
if (!confirm('Reset theme to default? This cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
themeLog.info('Resetting to default theme');
|
||||
await this.applyPreset('default');
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// SAVE OPERATIONS
|
||||
// ============================================================================
|
||||
|
||||
async saveTheme() {
|
||||
themeLog.info('Saving theme:', this.themeData);
|
||||
themeLog.warn('Resetting theme to default');
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.put(
|
||||
`/admin/vendor-themes/${this.vendorCode}`,
|
||||
this.themeData
|
||||
);
|
||||
const duration = Date.now() - startTime;
|
||||
const url = `/admin/vendor-themes/${this.vendorCode}`;
|
||||
window.LogConfig.logApiCall('DELETE', url, null, 'request');
|
||||
|
||||
if (response) {
|
||||
Utils.showToast('Theme saved successfully', 'success');
|
||||
themeLog.info(`Theme saved in ${duration}ms`);
|
||||
}
|
||||
await apiClient.delete(url);
|
||||
|
||||
window.LogConfig.logApiCall('DELETE', url, null, 'response');
|
||||
|
||||
// Reload theme data
|
||||
await this.loadTheme();
|
||||
|
||||
themeLog.info('Theme reset successfully');
|
||||
Utils.showToast('Theme reset to default', 'success');
|
||||
|
||||
} catch (error) {
|
||||
themeLog.error('Failed to save theme:', error);
|
||||
const message = error.response?.data?.detail || 'Failed to save theme';
|
||||
Utils.showToast(message, 'error');
|
||||
this.error = message;
|
||||
|
||||
window.LogConfig.logError(error, 'Reset Theme');
|
||||
Utils.showToast('Failed to reset theme', 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// HELPER METHODS
|
||||
// ============================================================================
|
||||
// ====================================================================
|
||||
// UTILITY METHODS
|
||||
// ====================================================================
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-';
|
||||
return Utils.formatDate(dateString);
|
||||
previewTheme() {
|
||||
themeLog.debug('Opening theme preview');
|
||||
const previewUrl = `/vendor/${this.vendor?.subdomain || this.vendorCode}`;
|
||||
window.open(previewUrl, '_blank');
|
||||
},
|
||||
|
||||
getPreviewStyle() {
|
||||
return {
|
||||
'--color-primary': this.themeData.colors.primary,
|
||||
'--color-secondary': this.themeData.colors.secondary,
|
||||
'--color-accent': this.themeData.colors.accent,
|
||||
'--color-background': this.themeData.colors.background,
|
||||
'--color-text': this.themeData.colors.text,
|
||||
'--color-border': this.themeData.colors.border,
|
||||
'--font-heading': this.themeData.fonts.heading,
|
||||
'--font-body': this.themeData.fonts.body,
|
||||
};
|
||||
updateColor(key, value) {
|
||||
themeLog.debug(`Color updated: ${key} = ${value}`);
|
||||
this.themeData.colors[key] = value;
|
||||
},
|
||||
|
||||
updateFont(type, value) {
|
||||
themeLog.debug(`Font updated: ${type} = ${value}`);
|
||||
this.themeData.fonts[type] = value;
|
||||
},
|
||||
|
||||
updateLayout(key, value) {
|
||||
themeLog.debug(`Layout updated: ${key} = ${value}`);
|
||||
this.themeData.layout[key] = value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>`,
|
||||
'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>`,
|
||||
'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
|
||||
'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>`,
|
||||
'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-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>`,
|
||||
|
||||
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