feat: dynamic login providers + profiler auto-trigger + refresh endpoint

This commit is contained in:
ZdenekSrotyr 2026-04-08 07:04:40 +02:00
parent 4bad893cb8
commit 3e3f84a00e
3 changed files with 68 additions and 3 deletions

View file

@ -96,3 +96,27 @@ async def get_metric(
return content return content
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Error parsing metric: {e}") raise HTTPException(status_code=500, detail=f"Error parsing metric: {e}")
@router.post("/profile/{table_name}/refresh")
async def refresh_profile(
table_name: str,
user: dict = Depends(get_current_user),
conn: duckdb.DuckDBPyConnection = Depends(_get_db),
):
"""Re-generate profile for a table on demand."""
from src.profiler import profile_table, TableInfo
data_dir = _get_data_dir()
extracts_dir = data_dir / "extracts"
candidates = list(extracts_dir.rglob(f"data/{table_name}.parquet"))
if not candidates:
raise HTTPException(status_code=404, detail=f"No parquet for '{table_name}'")
try:
table_info = TableInfo(name=table_name, table_id=table_name)
profile = profile_table(table_info, candidates[0], [], {}, {})
ProfileRepository(conn).save(table_name, profile)
return {"status": "ok", "table": table_name, "columns": len(profile.get("columns", {}))}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Profile failed: {e}")

View file

@ -122,6 +122,36 @@ print(json.dumps(result))
views = orch.rebuild() views = orch.rebuild()
print(f"[SYNC] Orchestrator rebuild: {{{', '.join(f'{k}: {len(v)}' for k, v in views.items())}}}", file=_sys.stderr, flush=True) print(f"[SYNC] Orchestrator rebuild: {{{', '.join(f'{k}: {len(v)}' for k, v in views.items())}}}", file=_sys.stderr, flush=True)
# Auto-profile synced tables (best-effort, don't fail sync on profile error)
try:
from src.profiler import profile_table, TableInfo
from src.repositories.profiles import ProfileRepository
data_dir = Path(os.environ.get("DATA_DIR", "./data"))
extracts_dir = data_dir / "extracts"
sys_conn = get_system_db()
try:
profile_repo = ProfileRepository(sys_conn)
profiled = 0
for source_name, table_names in views.items():
for table_name in table_names[:10]: # Limit per sync
pq_path = extracts_dir / source_name / "data" / f"{table_name}.parquet"
if not pq_path.exists():
continue
try:
table_info = TableInfo(name=table_name, table_id=table_name)
profile = profile_table(table_info, pq_path, [], {}, {})
profile_repo.save(table_name, profile)
profiled += 1
except Exception as pe:
print(f"[SYNC] Profile {table_name}: {pe}", file=_sys.stderr, flush=True)
print(f"[SYNC] Profiled {profiled} tables", file=_sys.stderr, flush=True)
finally:
sys_conn.close()
except Exception as e:
print(f"[SYNC] Profiler skipped: {e}", file=_sys.stderr, flush=True)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
print("[SYNC] Extractor timed out after 1800s", file=_sys.stderr, flush=True) print("[SYNC] Extractor timed out after 1800s", file=_sys.stderr, flush=True)
except Exception as e: except Exception as e:

View file

@ -151,9 +151,20 @@ async def index(request: Request, user: Optional[dict] = Depends(get_optional_us
@router.get("/login", response_class=HTMLResponse) @router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request): async def login_page(request: Request):
providers = [ providers = []
{"name": "google", "display_name": "Google", "icon": "google"}, try:
] from app.auth.providers.google import is_available as google_available
if google_available():
providers.append({"name": "google", "display_name": "Google", "icon": "google"})
except Exception:
pass
providers.append({"name": "password", "display_name": "Email & Password", "icon": "key"})
try:
from app.auth.providers.email import is_available as email_available
if email_available():
providers.append({"name": "email", "display_name": "Email Link", "icon": "mail"})
except Exception:
pass
ctx = _build_context(request, providers=providers) ctx = _build_context(request, providers=providers)
return templates.TemplateResponse(request, "login.html", ctx) return templates.TemplateResponse(request, "login.html", ctx)