- Add BottomSheet component for Google Maps-style restaurant detail on mobile (3-snap drag: 40%/55%/92%, velocity-based close, backdrop overlay) - Mobile map mode now full-screen with bottom sheet overlay for details - Collapsible filter panel on mobile with active filter badge count - Standardized cuisine taxonomy (46 categories: 한식|국밥, 일식|스시 etc.) with LLM remap endpoint and admin UI button - Enhanced search: keyword search now includes foods_mentioned + video title - Search results include channels array for frontend filtering - Channel filter moved to frontend filteredRestaurants (not API-level) - LLM extraction prompt updated for pipe-delimited region + cuisine taxonomy - Vector rebuild endpoint with rich JSON chunks per restaurant - Geolocation-based auto region selection on page load - Desktop filters split into two clean rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
3.4 KiB
Python
99 lines
3.4 KiB
Python
"""Daemon config & manual trigger API routes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
|
|
from api.deps import get_admin_user
|
|
from core.db import conn
|
|
from core import cache
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class DaemonConfigUpdate(BaseModel):
|
|
scan_enabled: bool | None = None
|
|
scan_interval_min: int | None = None
|
|
process_enabled: bool | None = None
|
|
process_interval_min: int | None = None
|
|
process_limit: int | None = None
|
|
|
|
|
|
@router.get("/config")
|
|
def get_config():
|
|
"""Get daemon config (read-only for all authenticated users)."""
|
|
with conn() as c:
|
|
cur = c.cursor()
|
|
cur.execute(
|
|
"SELECT scan_enabled, scan_interval_min, process_enabled, process_interval_min, "
|
|
"process_limit, last_scan_at, last_process_at, updated_at "
|
|
"FROM daemon_config WHERE id = 1"
|
|
)
|
|
row = cur.fetchone()
|
|
if not row:
|
|
return {}
|
|
return {
|
|
"scan_enabled": bool(row[0]),
|
|
"scan_interval_min": row[1],
|
|
"process_enabled": bool(row[2]),
|
|
"process_interval_min": row[3],
|
|
"process_limit": row[4],
|
|
"last_scan_at": str(row[5]) if row[5] else None,
|
|
"last_process_at": str(row[6]) if row[6] else None,
|
|
"updated_at": str(row[7]) if row[7] else None,
|
|
}
|
|
|
|
|
|
@router.put("/config")
|
|
def update_config(body: DaemonConfigUpdate, _admin: dict = Depends(get_admin_user)):
|
|
"""Update daemon schedule config (admin only)."""
|
|
sets = []
|
|
params: dict = {}
|
|
if body.scan_enabled is not None:
|
|
sets.append("scan_enabled = :se")
|
|
params["se"] = 1 if body.scan_enabled else 0
|
|
if body.scan_interval_min is not None:
|
|
sets.append("scan_interval_min = :si")
|
|
params["si"] = body.scan_interval_min
|
|
if body.process_enabled is not None:
|
|
sets.append("process_enabled = :pe")
|
|
params["pe"] = 1 if body.process_enabled else 0
|
|
if body.process_interval_min is not None:
|
|
sets.append("process_interval_min = :pi")
|
|
params["pi"] = body.process_interval_min
|
|
if body.process_limit is not None:
|
|
sets.append("process_limit = :pl")
|
|
params["pl"] = body.process_limit
|
|
if not sets:
|
|
return {"ok": True}
|
|
sets.append("updated_at = SYSTIMESTAMP")
|
|
sql = f"UPDATE daemon_config SET {', '.join(sets)} WHERE id = 1"
|
|
with conn() as c:
|
|
c.cursor().execute(sql, params)
|
|
return {"ok": True}
|
|
|
|
|
|
@router.post("/run/scan")
|
|
def run_scan(_admin: dict = Depends(get_admin_user)):
|
|
"""Manually trigger channel scan (admin only)."""
|
|
from core.youtube import scan_all_channels
|
|
new_count = scan_all_channels()
|
|
with conn() as c:
|
|
c.cursor().execute("UPDATE daemon_config SET last_scan_at = SYSTIMESTAMP WHERE id = 1")
|
|
if new_count > 0:
|
|
cache.flush()
|
|
return {"ok": True, "new_videos": new_count}
|
|
|
|
|
|
@router.post("/run/process")
|
|
def run_process(limit: int = 10, _admin: dict = Depends(get_admin_user)):
|
|
"""Manually trigger video processing (admin only)."""
|
|
from core.pipeline import process_pending
|
|
rest_count = process_pending(limit=limit)
|
|
with conn() as c:
|
|
c.cursor().execute("UPDATE daemon_config SET last_process_at = SYSTIMESTAMP WHERE id = 1")
|
|
if rest_count > 0:
|
|
cache.flush()
|
|
return {"ok": True, "restaurants_extracted": rest_count}
|