Backend enhancements: auth, channels, restaurants, daemon improvements

- Add admin auth dependency and role checks
- Expand channel and restaurant API routes
- Improve YouTube transcript fetching
- Enhance daemon worker with better error handling and scheduling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-09 10:59:22 +09:00
parent d6afb62c18
commit 6c47d3c57d
9 changed files with 208 additions and 42 deletions

View File

@@ -36,5 +36,22 @@ def login_google(body: GoogleLoginRequest):
@router.get("/me")
def get_me(current_user: dict = Depends(get_current_user)):
"""Return current authenticated user info."""
return current_user
"""Return current authenticated user info including admin status."""
from core.db import conn
user_id = current_user.get("sub") or current_user.get("id")
with conn() as c:
cur = c.cursor()
cur.execute(
"SELECT id, email, nickname, avatar_url, is_admin FROM tasteby_users WHERE id = :id",
{"id": user_id},
)
row = cur.fetchone()
if not row:
raise HTTPException(404, "User not found")
return {
"id": row[0],
"email": row[1],
"nickname": row[2],
"avatar_url": row[3],
"is_admin": bool(row[4]),
}

View File

@@ -5,10 +5,12 @@ from __future__ import annotations
import asyncio
from concurrent.futures import ThreadPoolExecutor
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from core import youtube
from api.deps import get_admin_user
from core import youtube, cache
_executor = ThreadPoolExecutor(max_workers=4)
@@ -23,13 +25,20 @@ class ChannelCreate(BaseModel):
@router.get("")
def list_channels():
return youtube.get_active_channels()
key = cache.make_key("channels")
cached = cache.get(key)
if cached is not None:
return cached
result = youtube.get_active_channels()
cache.set(key, result)
return result
@router.post("", status_code=201)
def create_channel(body: ChannelCreate):
def create_channel(body: ChannelCreate, _admin: dict = Depends(get_admin_user)):
try:
row_id = youtube.add_channel(body.channel_id, body.channel_name, body.title_filter)
cache.flush()
return {"id": row_id, "channel_id": body.channel_id}
except Exception as e:
if "UQ_CHANNELS_CID" in str(e).upper():
@@ -63,12 +72,15 @@ def _do_scan(channel_id: str, full: bool):
if not full and new_in_page == 0 and total_fetched > 50:
break
filtered = total_fetched - len(candidates) - len([v for v in candidates if v["video_id"] in existing_vids])
new_count = youtube.save_videos_batch(ch["id"], candidates)
return {"total_fetched": total_fetched, "new_videos": new_count}
if new_count > 0:
cache.flush()
return {"total_fetched": total_fetched, "new_videos": new_count, "filtered": filtered if title_filter else 0}
@router.post("/{channel_id}/scan")
async def scan_channel(channel_id: str, full: bool = False):
async def scan_channel(channel_id: str, full: bool = False, _admin: dict = Depends(get_admin_user)):
"""Trigger a scan for new videos from this channel (non-blocking)."""
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(_executor, _do_scan, channel_id, full)
@@ -78,7 +90,7 @@ async def scan_channel(channel_id: str, full: bool = False):
@router.delete("/{channel_id:path}")
def delete_channel(channel_id: str):
def delete_channel(channel_id: str, _admin: dict = Depends(get_admin_user)):
"""Deactivate a channel. Accepts channel_id or DB id."""
deleted = youtube.deactivate_channel(channel_id)
if not deleted:
@@ -86,4 +98,5 @@ def delete_channel(channel_id: str):
deleted = youtube.deactivate_channel_by_db_id(channel_id)
if not deleted:
raise HTTPException(404, "Channel not found")
cache.flush()
return {"ok": True}

View File

@@ -2,9 +2,10 @@
from __future__ import annotations
from fastapi import APIRouter, HTTPException, Query
from fastapi import APIRouter, Depends, HTTPException, Query
from core import restaurant
from api.deps import get_admin_user
from core import restaurant, cache
router = APIRouter()
@@ -17,19 +18,30 @@ def list_restaurants(
region: str | None = None,
channel: str | None = None,
):
return restaurant.get_all(limit=limit, offset=offset, cuisine=cuisine, region=region, channel=channel)
key = cache.make_key("restaurants", f"l={limit}", f"o={offset}", f"c={cuisine}", f"r={region}", f"ch={channel}")
cached = cache.get(key)
if cached is not None:
return cached
result = restaurant.get_all(limit=limit, offset=offset, cuisine=cuisine, region=region, channel=channel)
cache.set(key, result)
return result
@router.get("/{restaurant_id}")
def get_restaurant(restaurant_id: str):
key = cache.make_key("restaurant", restaurant_id)
cached = cache.get(key)
if cached is not None:
return cached
r = restaurant.get_by_id(restaurant_id)
if not r:
raise HTTPException(404, "Restaurant not found")
cache.set(key, r)
return r
@router.put("/{restaurant_id}")
def update_restaurant(restaurant_id: str, body: dict):
def update_restaurant(restaurant_id: str, body: dict, _admin: dict = Depends(get_admin_user)):
from core.db import conn
r = restaurant.get_by_id(restaurant_id)
if not r:
@@ -49,11 +61,12 @@ def update_restaurant(restaurant_id: str, body: dict):
sql = f"UPDATE restaurants SET {', '.join(sets)} WHERE id = :rid"
with conn() as c:
c.cursor().execute(sql, params)
cache.flush()
return {"ok": True}
@router.delete("/{restaurant_id}")
def delete_restaurant(restaurant_id: str):
def delete_restaurant(restaurant_id: str, _admin: dict = Depends(get_admin_user)):
from core.db import conn
r = restaurant.get_by_id(restaurant_id)
if not r:
@@ -64,12 +77,19 @@ def delete_restaurant(restaurant_id: str):
cur.execute("DELETE FROM user_reviews WHERE restaurant_id = :rid", {"rid": restaurant_id})
cur.execute("DELETE FROM video_restaurants WHERE restaurant_id = :rid", {"rid": restaurant_id})
cur.execute("DELETE FROM restaurants WHERE id = :rid", {"rid": restaurant_id})
cache.flush()
return {"ok": True}
@router.get("/{restaurant_id}/videos")
def get_restaurant_videos(restaurant_id: str):
key = cache.make_key("restaurant_videos", restaurant_id)
cached = cache.get(key)
if cached is not None:
return cached
r = restaurant.get_by_id(restaurant_id)
if not r:
raise HTTPException(404, "Restaurant not found")
return restaurant.get_video_links(restaurant_id)
result = restaurant.get_video_links(restaurant_id)
cache.set(key, result)
return result