Spaces:
Sleeping
Sleeping
File size: 6,819 Bytes
41d16b3 214c544 ac7ea7c 66006d5 41d16b3 4595db8 70d9e6c 669f7f8 ac7ea7c 214c544 ac7ea7c 41d16b3 99e1f27 41d16b3 214c544 41d16b3 bec7397 41d16b3 bec7397 41d16b3 99e1f27 41d16b3 bec7397 41d16b3 214c544 41d16b3 ac7ea7c 214c544 99e1f27 bec7397 41d16b3 8354653 214c544 8354653 214c544 8354653 214c544 8354653 214c544 8354653 41d16b3 214c544 41d16b3 66006d5 70d9e6c 669f7f8 ac7ea7c 4595db8 214c544 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | import logging
from fastapi import FastAPI
from sqlalchemy import text as sa_text
from ..database import engine
from .config import IS_DDA_MODE, ensure_library_dirs, get_storage_root, is_hf_hosted
from .admin_routes import router as admin_router
from .jobs_routes import router as jobs_router
from .library_routes import router as library_router
from .local_routes import router as local_router
from .reports_routes import router as reports_router
from .review_routes import router as review_router
from .training_routes import router as training_router
from .tree.routes import router as tree_router
from .dda_auth import seed_dda_admin
logger = logging.getLogger(__name__)
def _migrate_detection_jobs_nullable() -> None:
"""SQLite legacy schema required image asset FKs; tree library jobs use paths only."""
try:
with engine.connect() as conn:
rows = conn.execute(sa_text("PRAGMA table_info(dda_detection_jobs)")).fetchall()
if not rows:
return
by_name = {r[1]: r for r in rows}
base_col = by_name.get("base_image_id")
if not base_col or base_col[3] == 0:
return
logger.info("Migrating dda_detection_jobs to allow NULL image IDs for path-based jobs")
conn.execute(sa_text("PRAGMA foreign_keys=OFF"))
conn.execute(sa_text("""
CREATE TABLE dda_detection_jobs_new (
id INTEGER NOT NULL PRIMARY KEY,
status VARCHAR(32),
base_image_id INTEGER,
comparison_image_id INTEGER,
method VARCHAR(64),
params_json TEXT,
run_id INTEGER,
error_message TEXT,
notify_email VARCHAR(255),
created_by INTEGER,
started_at DATETIME,
completed_at DATETIME,
created_at DATETIME,
FOREIGN KEY(base_image_id) REFERENCES dda_image_assets (id),
FOREIGN KEY(comparison_image_id) REFERENCES dda_image_assets (id),
FOREIGN KEY(run_id) REFERENCES detection_runs (id),
FOREIGN KEY(created_by) REFERENCES users (id)
)
"""))
conn.execute(sa_text("""
INSERT INTO dda_detection_jobs_new (
id, status, base_image_id, comparison_image_id, method, params_json,
run_id, error_message, notify_email, created_by,
started_at, completed_at, created_at
)
SELECT
id, status, base_image_id, comparison_image_id, method, params_json,
run_id, error_message, notify_email, created_by,
started_at, completed_at, created_at
FROM dda_detection_jobs
"""))
conn.execute(sa_text("DROP TABLE dda_detection_jobs"))
conn.execute(sa_text("ALTER TABLE dda_detection_jobs_new RENAME TO dda_detection_jobs"))
conn.execute(sa_text("PRAGMA foreign_keys=ON"))
conn.commit()
except Exception as exc:
logger.warning("Detection jobs nullable migration skipped: %s", exc)
def init_dda_database():
"""Run DDA-specific startup tasks (dirs, seed, migrations)."""
if not IS_DDA_MODE:
return
ensure_library_dirs()
get_storage_root().mkdir(parents=True, exist_ok=True)
try:
with engine.connect() as conn:
for stmt in (
"ALTER TABLE users ADD COLUMN role VARCHAR(32) DEFAULT 'analyst'",
"ALTER TABLE detection_runs ADD COLUMN after_full_path VARCHAR(512) DEFAULT ''",
):
try:
conn.execute(sa_text(stmt))
conn.commit()
except Exception:
conn.rollback()
try:
conn.execute(sa_text(
"CREATE UNIQUE INDEX IF NOT EXISTS ix_dda_region_reviews_run_region "
"ON dda_region_reviews (run_id, region_id)"
))
conn.commit()
except Exception:
conn.rollback()
_migrate_detection_jobs_nullable()
except Exception as exc:
logger.warning("DDA schema migration skipped: %s", exc)
from ..database import Base, SessionLocal
from .tree.models import AuditLog, ImageLibrary, TreeNode # noqa: F401
try:
Base.metadata.create_all(bind=engine, tables=[
TreeNode.__table__,
ImageLibrary.__table__,
AuditLog.__table__,
])
except Exception as exc:
logger.warning("Tree table create skipped: %s", exc)
db = SessionLocal()
try:
seed_dda_admin(db)
from .tree.migration import run_tree_migration
mig = run_tree_migration(db)
logger.info("Tree migration: %s", mig)
try:
from .tree.sync_service import sync_from_filesystem
sync_stats = sync_from_filesystem(db)
logger.info("Filesystem sync at startup: %s", sync_stats)
except Exception as exc:
logger.warning("Filesystem sync at startup failed: %s", exc)
from .job_runner import reconcile_stale_jobs
reconcile_stale_jobs(db)
finally:
db.close()
try:
from .tree.image_service import list_all_images
from ..database import SessionLocal as SL
sdb = SL()
try:
total = len(list_all_images(sdb))
finally:
sdb.close()
logger.info(
"DDA tree library ready (hosted=%s): %d images, storage=%s",
is_hf_hosted(),
total,
get_storage_root(),
)
except Exception as exc:
logger.warning("Tree library scan at startup failed: %s", exc)
def setup_dda(app: FastAPI) -> None:
if not IS_DDA_MODE:
logger.info("APP_MODE=legacy — DDA routes disabled")
return
app.include_router(tree_router, prefix="/api/dda", tags=["dda-tree"])
app.include_router(library_router, prefix="/api/dda", tags=["dda"])
app.include_router(jobs_router, prefix="/api/dda", tags=["dda-jobs"])
app.include_router(reports_router, prefix="/api/dda", tags=["dda-reports"])
app.include_router(review_router, prefix="/api/dda", tags=["dda-review"])
app.include_router(training_router, prefix="/api/dda", tags=["dda-training"])
app.include_router(admin_router, prefix="/api/dda", tags=["dda-admin"])
app.include_router(local_router, prefix="/api/dda", tags=["dda-local"])
logger.info("APP_MODE=dda — DDA routes enabled (tree, library, jobs, reports, review, training, admin, local)")
|