"""User review DB operations.""" from __future__ import annotations from datetime import date import oracledb from core.db import conn def create_review( user_id: str, restaurant_id: str, rating: float, review_text: str | None = None, visited_at: date | None = None, ) -> dict: """Create a new review. Returns the created review dict.""" sql = """ INSERT INTO user_reviews (user_id, restaurant_id, rating, review_text, visited_at) VALUES (:user_id, :restaurant_id, :rating, :review_text, :visited_at) RETURNING id INTO :out_id """ with conn() as c: cur = c.cursor() out_id = cur.var(oracledb.STRING) cur.execute(sql, { "user_id": user_id, "restaurant_id": restaurant_id, "rating": rating, "review_text": review_text, "visited_at": visited_at, "out_id": out_id, }) new_id = out_id.getvalue()[0] return get_review_by_id(new_id) def update_review( review_id: str, user_id: str, rating: float | None = None, review_text: str | None = None, visited_at: date | None = None, ) -> dict: """Update an existing review. Only the owner can update. Returns the updated review dict, or None if not found / not owner. """ sql = """ UPDATE user_reviews SET rating = COALESCE(:rating, rating), review_text = COALESCE(:review_text, review_text), visited_at = COALESCE(:visited_at, visited_at), updated_at = SYSTIMESTAMP WHERE id = :id AND user_id = :user_id """ with conn() as c: cur = c.cursor() cur.execute(sql, { "rating": rating, "review_text": review_text, "visited_at": visited_at, "id": review_id, "user_id": user_id, }) if cur.rowcount == 0: return None return get_review_by_id(review_id) def delete_review(review_id: str, user_id: str) -> bool: """Delete a review. Only the owner can delete. Returns True if deleted.""" sql = "DELETE FROM user_reviews WHERE id = :id AND user_id = :user_id" with conn() as c: cur = c.cursor() cur.execute(sql, {"id": review_id, "user_id": user_id}) return cur.rowcount > 0 def get_review_by_id(review_id: str) -> dict | None: """Get a single review by ID.""" sql = """ SELECT r.id, r.user_id, r.restaurant_id, r.rating, r.review_text, r.visited_at, r.created_at, r.updated_at, u.nickname, u.avatar_url FROM user_reviews r JOIN tasteby_users u ON u.id = r.user_id WHERE r.id = :id """ with conn() as c: cur = c.cursor() cur.execute(sql, {"id": review_id}) row = cur.fetchone() if not row: return None return _row_to_dict(row) def get_reviews_for_restaurant( restaurant_id: str, limit: int = 20, offset: int = 0, ) -> list[dict]: """List reviews for a restaurant, including user nickname/avatar.""" sql = """ SELECT r.id, r.user_id, r.restaurant_id, r.rating, r.review_text, r.visited_at, r.created_at, r.updated_at, u.nickname, u.avatar_url FROM user_reviews r JOIN tasteby_users u ON u.id = r.user_id WHERE r.restaurant_id = :restaurant_id ORDER BY r.created_at DESC OFFSET :off ROWS FETCH NEXT :lim ROWS ONLY """ with conn() as c: cur = c.cursor() cur.execute(sql, { "restaurant_id": restaurant_id, "off": offset, "lim": limit, }) return [_row_to_dict(row) for row in cur.fetchall()] def get_user_reviews( user_id: str, limit: int = 20, offset: int = 0, ) -> list[dict]: """List reviews by a specific user.""" sql = """ SELECT r.id, r.user_id, r.restaurant_id, r.rating, r.review_text, r.visited_at, r.created_at, r.updated_at, u.nickname, u.avatar_url FROM user_reviews r JOIN tasteby_users u ON u.id = r.user_id WHERE r.user_id = :user_id ORDER BY r.created_at DESC OFFSET :off ROWS FETCH NEXT :lim ROWS ONLY """ with conn() as c: cur = c.cursor() cur.execute(sql, { "user_id": user_id, "off": offset, "lim": limit, }) return [_row_to_dict(row) for row in cur.fetchall()] def get_restaurant_avg_rating(restaurant_id: str) -> dict: """Get average rating and review count for a restaurant.""" sql = """ SELECT ROUND(AVG(rating), 1) AS avg_rating, COUNT(*) AS review_count FROM user_reviews WHERE restaurant_id = :restaurant_id """ with conn() as c: cur = c.cursor() cur.execute(sql, {"restaurant_id": restaurant_id}) row = cur.fetchone() return { "avg_rating": float(row[0]) if row[0] else None, "review_count": int(row[1]), } def _row_to_dict(row) -> dict: """Convert a review query row to a dict.""" review_text = row[4] if hasattr(review_text, "read"): review_text = review_text.read() return { "id": row[0], "user_id": row[1], "restaurant_id": row[2], "rating": float(row[3]), "review_text": review_text, "visited_at": row[5].isoformat() if row[5] else None, "created_at": row[6].isoformat() if row[6] else None, "updated_at": row[7].isoformat() if row[7] else None, "user_nickname": row[8], "user_avatar_url": row[9], }