Add admin features, responsive UI, user reviews, visit stats, and channel-colored markers

- Admin: video management with Google Maps match status, manual restaurant mapping, restaurant remap on name change
- Admin: user management tab with favorites/reviews detail
- Admin: channel deletion fix for IDs with slashes
- Frontend: responsive mobile layout (map top, list bottom, 2-row header)
- Frontend: channel-colored map markers with legend
- Frontend: my reviews list, favorites toggle, visit counter overlay
- Frontend: force light mode for dark theme devices
- Backend: visit tracking (site_visits table), user reviews endpoint
- Backend: bulk transcript/extract streaming, geocode key fixes
- Nginx config for production deployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-07 14:52:20 +09:00
parent 36bec10bd0
commit 3694730501
27 changed files with 4346 additions and 189 deletions

View File

@@ -95,3 +95,74 @@ def list_my_reviews(
):
"""List current user's reviews."""
return review.get_user_reviews(current_user["sub"], limit=limit, offset=offset)
# --- Favorites ---
@router.get("/restaurants/{restaurant_id}/favorite")
def get_favorite_status(
restaurant_id: str,
current_user: dict = Depends(get_current_user),
):
"""Check if current user has favorited this restaurant."""
from core.db import conn
with conn() as c:
cur = c.cursor()
cur.execute(
"SELECT id FROM user_favorites WHERE user_id = :u AND restaurant_id = :r",
{"u": current_user["sub"], "r": restaurant_id},
)
return {"favorited": cur.fetchone() is not None}
@router.post("/restaurants/{restaurant_id}/favorite")
def toggle_favorite(
restaurant_id: str,
current_user: dict = Depends(get_current_user),
):
"""Toggle favorite. Returns new state."""
from core.db import conn
user_id = current_user["sub"]
with conn() as c:
cur = c.cursor()
cur.execute(
"SELECT id FROM user_favorites WHERE user_id = :u AND restaurant_id = :r",
{"u": user_id, "r": restaurant_id},
)
row = cur.fetchone()
if row:
cur.execute("DELETE FROM user_favorites WHERE id = :fid", {"fid": row[0]})
return {"favorited": False}
else:
cur.execute(
"INSERT INTO user_favorites (user_id, restaurant_id) VALUES (:u, :r)",
{"u": user_id, "r": restaurant_id},
)
return {"favorited": True}
@router.get("/users/me/favorites")
def list_my_favorites(
current_user: dict = Depends(get_current_user),
):
"""List current user's favorite restaurants."""
from core.db import conn
sql = """
SELECT r.id, r.name, r.address, r.region, r.latitude, r.longitude,
r.cuisine_type, r.price_range, r.google_place_id,
r.business_status, r.rating, r.rating_count,
f.created_at
FROM user_favorites f
JOIN restaurants r ON r.id = f.restaurant_id
WHERE f.user_id = :u
ORDER BY f.created_at DESC
"""
with conn() as c:
cur = c.cursor()
cur.execute(sql, {"u": current_user["sub"]})
cols = [d[0].lower() for d in cur.description]
rows = [dict(zip(cols, row)) for row in cur.fetchall()]
for r in rows:
if r.get("created_at"):
r["created_at"] = r["created_at"].isoformat()
return rows