Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 124 additions & 191 deletions backend/init_db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import sys
import os
from pathlib import Path
from sqlalchemy import text, inspect
import logging

# Add project root to path
current_file = Path(__file__).resolve()
Expand All @@ -11,204 +13,135 @@
from backend.database import engine, Base
from backend.models import *

logger = logging.getLogger(__name__)

def init_db():
print("Creating tables...")
Base.metadata.create_all(bind=engine)
print("Tables created.")

if __name__ == "__main__":
init_db()
from sqlalchemy import text
from backend.database import engine
import logging

logger = logging.getLogger(__name__)

def migrate_db():
"""
Perform database migrations.
This is a simple MVP migration strategy.
Perform database migrations using SQLAlchemy inspection.
This prevents transaction aborts in Postgres when columns already exist.
"""
try:
with engine.connect() as conn:
# Check for upvotes column and add if missing
try:
# SQLite doesn't support IF NOT EXISTS in ALTER TABLE
# So we just try to add it and ignore error if it exists
conn.execute(text("ALTER TABLE issues ADD COLUMN upvotes INTEGER DEFAULT 0"))
logger.info("Migrated database: Added upvotes column.")
except Exception:
# Column likely already exists
pass

# Check if index exists or create it
try:
conn.execute(text("CREATE INDEX ix_issues_upvotes ON issues (upvotes)"))
logger.info("Migrated database: Added index on upvotes column.")
except Exception:
# Index likely already exists
pass

# Add index on created_at for faster sorting
try:
conn.execute(text("CREATE INDEX ix_issues_created_at ON issues (created_at)"))
logger.info("Migrated database: Added index on created_at column.")
except Exception:
# Index likely already exists
pass

# Add index on status for faster filtering
try:
conn.execute(text("CREATE INDEX ix_issues_status ON issues (status)"))
logger.info("Migrated database: Added index on status column.")
except Exception:
# Index likely already exists
pass

# Add latitude column
try:
conn.execute(text("ALTER TABLE issues ADD COLUMN latitude FLOAT"))
print("Migrated database: Added latitude column.")
except Exception:
pass

# Add longitude column
try:
conn.execute(text("ALTER TABLE issues ADD COLUMN longitude FLOAT"))
print("Migrated database: Added longitude column.")
except Exception:
pass

# Add index on latitude for faster spatial queries
try:
conn.execute(text("CREATE INDEX ix_issues_latitude ON issues (latitude)"))
logger.info("Migrated database: Added index on latitude column.")
except Exception:
# Index likely already exists
pass

# Add index on longitude for faster spatial queries
try:
conn.execute(text("CREATE INDEX ix_issues_longitude ON issues (longitude)"))
logger.info("Migrated database: Added index on longitude column.")
except Exception:
# Index likely already exists
pass

# Add composite index for optimized spatial+status queries
try:
conn.execute(text("CREATE INDEX ix_issues_status_lat_lon ON issues (status, latitude, longitude)"))
logger.info("Migrated database: Added composite index on status, latitude, longitude.")
except Exception:
# Index likely already exists
pass

# Add location column
try:
conn.execute(text("ALTER TABLE issues ADD COLUMN location VARCHAR"))
print("Migrated database: Added location column.")
except Exception:
pass

# Add action_plan column
try:
conn.execute(text("ALTER TABLE issues ADD COLUMN action_plan TEXT"))
print("Migrated database: Added action_plan column.")
except Exception:
pass

# Add integrity_hash column for blockchain feature
try:
conn.execute(text("ALTER TABLE issues ADD COLUMN integrity_hash VARCHAR"))
print("Migrated database: Added integrity_hash column.")
except Exception:
pass

# Add index on user_email
try:
conn.execute(text("CREATE INDEX ix_issues_user_email ON issues (user_email)"))
logger.info("Migrated database: Added index on user_email column.")
except Exception:
# Index likely already exists
pass

# --- Grievance Migrations ---
# Add latitude column to grievances
try:
conn.execute(text("ALTER TABLE grievances ADD COLUMN latitude FLOAT"))
logger.info("Migrated database: Added latitude column to grievances.")
except Exception:
pass

# Add longitude column to grievances
try:
conn.execute(text("ALTER TABLE grievances ADD COLUMN longitude FLOAT"))
logger.info("Migrated database: Added longitude column to grievances.")
except Exception:
pass

# Add address column to grievances
try:
conn.execute(text("ALTER TABLE grievances ADD COLUMN address VARCHAR"))
logger.info("Migrated database: Added address column to grievances.")
except Exception:
pass

# Add index on latitude (grievances)
try:
conn.execute(text("CREATE INDEX ix_grievances_latitude ON grievances (latitude)"))
except Exception:
pass

# Add index on longitude (grievances)
try:
conn.execute(text("CREATE INDEX ix_grievances_longitude ON grievances (longitude)"))
except Exception:
pass

# Add composite index for spatial+status (grievances)
try:
conn.execute(text("CREATE INDEX ix_grievances_status_lat_lon ON grievances (status, latitude, longitude)"))
logger.info("Migrated database: Added composite index on status, latitude, longitude for grievances.")
except Exception:
pass

# Add composite index for status+jurisdiction (grievances)
try:
conn.execute(text("CREATE INDEX ix_grievances_status_jurisdiction ON grievances (status, current_jurisdiction_id)"))
logger.info("Migrated database: Added composite index on status, jurisdiction for grievances.")
except Exception:
pass

# Add issue_id column to grievances
try:
conn.execute(text("ALTER TABLE grievances ADD COLUMN issue_id INTEGER"))
logger.info("Migrated database: Added issue_id column to grievances.")
except Exception:
pass

# Add index on issue_id (grievances)
try:
conn.execute(text("CREATE INDEX ix_grievances_issue_id ON grievances (issue_id)"))
logger.info("Migrated database: Added index on issue_id for grievances.")
except Exception:
pass

# Add index on assigned_authority (grievances)
try:
conn.execute(text("CREATE INDEX ix_grievances_assigned_authority ON grievances (assigned_authority)"))
logger.info("Migrated database: Added index on assigned_authority for grievances.")
except Exception:
pass

# Add composite index for category+status (grievances) - Optimized for filtering
try:
conn.execute(text("CREATE INDEX ix_grievances_category_status ON grievances (category, status)"))
logger.info("Migrated database: Added composite index on category, status for grievances.")
except Exception:
pass

conn.commit()
logger.info("Database migration check completed.")
inspector = inspect(engine)

# Helper to check column existence
def column_exists(table, column):
if not inspector.has_table(table):
return False
columns = [c["name"] for c in inspector.get_columns(table)]
return column in columns

# Helper to check index existence (by name)
def index_exists(table, index_name):
if not inspector.has_table(table):
return False
indexes = [i["name"] for i in inspector.get_indexes(table)]
return index_name in indexes

with engine.begin() as conn:
# Issues Table Migrations
if inspector.has_table("issues"):
if not column_exists("issues", "upvotes"):
conn.execute(text("ALTER TABLE issues ADD COLUMN upvotes INTEGER DEFAULT 0"))
logger.info("Added upvotes column to issues")

if not column_exists("issues", "latitude"):
conn.execute(text("ALTER TABLE issues ADD COLUMN latitude FLOAT"))
logger.info("Added latitude column to issues")

if not column_exists("issues", "longitude"):
conn.execute(text("ALTER TABLE issues ADD COLUMN longitude FLOAT"))
logger.info("Added longitude column to issues")

if not column_exists("issues", "location"):
conn.execute(text("ALTER TABLE issues ADD COLUMN location VARCHAR"))
logger.info("Added location column to issues")

if not column_exists("issues", "action_plan"):
conn.execute(text("ALTER TABLE issues ADD COLUMN action_plan TEXT"))
logger.info("Added action_plan column to issues")

if not column_exists("issues", "integrity_hash"):
conn.execute(text("ALTER TABLE issues ADD COLUMN integrity_hash VARCHAR"))
logger.info("Added integrity_hash column to issues")

if not column_exists("issues", "previous_integrity_hash"):
conn.execute(text("ALTER TABLE issues ADD COLUMN previous_integrity_hash VARCHAR"))
logger.info("Added previous_integrity_hash column to issues")

# Indexes (using IF NOT EXISTS syntax where supported or check first)
if not index_exists("issues", "ix_issues_upvotes"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_upvotes ON issues (upvotes)"))

if not index_exists("issues", "ix_issues_created_at"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_created_at ON issues (created_at)"))

if not index_exists("issues", "ix_issues_status"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_status ON issues (status)"))

if not index_exists("issues", "ix_issues_latitude"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_latitude ON issues (latitude)"))

if not index_exists("issues", "ix_issues_longitude"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_longitude ON issues (longitude)"))

if not index_exists("issues", "ix_issues_status_lat_lon"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_status_lat_lon ON issues (status, latitude, longitude)"))

if not index_exists("issues", "ix_issues_user_email"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_issues_user_email ON issues (user_email)"))

# Grievances Table Migrations
if inspector.has_table("grievances"):
if not column_exists("grievances", "latitude"):
conn.execute(text("ALTER TABLE grievances ADD COLUMN latitude FLOAT"))
logger.info("Added latitude column to grievances")

if not column_exists("grievances", "longitude"):
conn.execute(text("ALTER TABLE grievances ADD COLUMN longitude FLOAT"))
logger.info("Added longitude column to grievances")

if not column_exists("grievances", "address"):
conn.execute(text("ALTER TABLE grievances ADD COLUMN address VARCHAR"))
logger.info("Added address column to grievances")

if not column_exists("grievances", "issue_id"):
conn.execute(text("ALTER TABLE grievances ADD COLUMN issue_id INTEGER"))
logger.info("Added issue_id column to grievances")

# Indexes
if not index_exists("grievances", "ix_grievances_latitude"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_latitude ON grievances (latitude)"))

if not index_exists("grievances", "ix_grievances_longitude"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_longitude ON grievances (longitude)"))

if not index_exists("grievances", "ix_grievances_status_lat_lon"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_status_lat_lon ON grievances (status, latitude, longitude)"))

if not index_exists("grievances", "ix_grievances_status_jurisdiction"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_status_jurisdiction ON grievances (status, current_jurisdiction_id)"))

if not index_exists("grievances", "ix_grievances_issue_id"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_issue_id ON grievances (issue_id)"))

if not index_exists("grievances", "ix_grievances_assigned_authority"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_assigned_authority ON grievances (assigned_authority)"))

if not index_exists("grievances", "ix_grievances_category_status"):
conn.execute(text("CREATE INDEX IF NOT EXISTS ix_grievances_category_status ON grievances (category, status)"))

logger.info("Database migration check completed successfully.")

except Exception as e:
logger.error(f"Database migration error: {e}")
logger.error(f"Database migration error: {e}", exc_info=True)
# Re-raise to alert deployment failure if migration is critical
# raise e
Comment on lines 140 to +143
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Swallowed migration errors will cause silent failures in production.

The commented-out raise e on Line 143 means any migration failure (e.g., connection issue, permission error, invalid SQL) is logged but otherwise ignored. The application will start with an incomplete schema, leading to hard-to-debug runtime crashes when code references missing columns or indexes.

If the intent is to tolerate pre-existing schema during development, the idempotent checks already handle that. The re-raise should be active so that genuine failures (permission denied, connectivity, syntax errors) surface immediately during deployment.

Proposed fix
     except Exception as e:
         logger.error(f"Database migration error: {e}", exc_info=True)
-        # Re-raise to alert deployment failure if migration is critical
-        # raise e
+        raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/init_db.py` around lines 140 - 143, The exception handler in the
migration block currently logs errors then swallows them because the `raise e`
is commented out; restore re-raising so migration failures abort startup. Locate
the except block that logs via `logger.error(f"Database migration error: {e}",
exc_info=True)` and replace the commented out `# raise e` with an active `raise`
(use plain `raise` to preserve the original traceback) so genuine
migration/connection/permission errors propagate and fail deployment.


if __name__ == "__main__":
init_db()
migrate_db()