Files
tasteby/backend/api/routes/daemon.py
joungmin 2bddb0f764 UX improvements: mobile bottom sheet, cuisine taxonomy, search enhancements
- 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>
2026-03-09 10:54:28 +09:00

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}