revamping frontend logging system and reorganising documentation

This commit is contained in:
2025-10-28 21:07:26 +01:00
parent 5c80ba17c5
commit b0cc0385f8
68 changed files with 3481 additions and 624 deletions

View File

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

View File

@@ -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')

View File

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

View File