feat: dynamic login providers + profiler auto-trigger + refresh endpoint
This commit is contained in:
parent
4bad893cb8
commit
3e3f84a00e
3 changed files with 68 additions and 3 deletions
|
|
@ -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}")
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue