Spaces:
Running
Running
| """Content module""" | |
| import time | |
| from db import get_service_client | |
| import engine_user | |
| from engineHelper import parse_db_timestamp, upload_media_to_cloudinary | |
| import threading | |
| # In-memory caches (simple dict caches) | |
| _avatar_cache = {} # username -> (avatar_url, timestamp) | |
| _post_likes_cache = {} # post_id -> (likes_count, timestamp) | |
| _cache_ttl = 60 # seconds | |
| _cache_lock = threading.Lock() | |
| def _get_cached(cache, key, ttl=None): | |
| ttl = ttl or _cache_ttl | |
| with _cache_lock: | |
| if key in cache: | |
| value, ts = cache[key] | |
| if time.time() - ts < ttl: | |
| return value | |
| del cache[key] | |
| return None | |
| def _set_cache(cache, key, value): | |
| with _cache_lock: | |
| cache[key] = (value, time.time()) | |
| # ======================== | |
| # POSTS | |
| # ======================== | |
| def create_post(username, content, media_path): | |
| sb = get_service_client() | |
| if media_path and media_path.startswith("data:"): | |
| media_path = upload_media_to_cloudinary(media_path) | |
| result = sb.table("posts").insert({ | |
| "username": username, "station_name": "", "content": content, | |
| "media_path": media_path, "comments_count": 0, "likes_count": 0 | |
| }).execute() | |
| row = result.data[0] | |
| avatar = engine_user.get_avatar(username) | |
| return { | |
| "id": str(row["id"]), "username": username, "content": content, | |
| "media_path": media_path, "timestamp": str(row["timestamp"]), "comments_count": "0", | |
| "likes_count": "0", "station_name": "", "avatar": avatar | |
| } | |
| def delete_post(post_id, username): | |
| sb = get_service_client() | |
| # Check ownership | |
| rows = sb.table("posts").select("id").eq("id", post_id).eq("username", username).execute() | |
| if not rows.data: return False | |
| # Cascading deletes handle comments and likes! | |
| sb.table("posts").delete().eq("id", post_id).eq("username", username).execute() | |
| return True | |
| def get_feed(viewer=""): | |
| sb = get_service_client() | |
| rows = sb.table("posts").select("id, username, content, media_path, timestamp, comments_count, likes_count, station_name").order("timestamp", desc=True).execute() | |
| # Get blocking set | |
| restricted_users = engine_user.get_blocking_relationship_usernames(viewer) if viewer else set() | |
| # Normalize restricted users to lowercase for faster case-insensitive lookup | |
| restricted_users_low = {u.lower() for u in restricted_users if u} | |
| viewer_low = viewer.lower() if viewer else "" | |
| # Get station admins for station posts filtering | |
| unique_stations = list(set(r["station_name"] for r in rows.data if r.get("station_name"))) | |
| station_admin_map = {} | |
| if unique_stations: | |
| try: | |
| import engine_station | |
| stations_res = sb.table("stations").select("station_name, admin, profile_pic").in_("station_name", unique_stations).execute() | |
| for sr in stations_res.data: | |
| station_admin_map[sr["station_name"]] = (sr["admin"], sr["profile_pic"] or "") | |
| except: | |
| pass | |
| results = [] | |
| # Pre-fetch likes if viewer is provided | |
| liked_post_ids = [] | |
| if viewer: | |
| likes_res = sb.table("likes").select("post_id").eq("username", viewer).execute() | |
| liked_post_ids = [str(l["post_id"]) for l in likes_res.data] | |
| # Performance optimization: pre-fetch visibility and follow status for all unique users | |
| unique_users = list(set(r["username"] for r in rows.data if r.get("username") and not r.get("station_name"))) | |
| visibility_map = {} | |
| following_set_low = set() | |
| if unique_users: | |
| try: | |
| users_res = sb.table("users").select("username, visibility").in_("username", unique_users).execute() | |
| for ur in users_res.data: | |
| visibility_map[ur["username"]] = int(ur["visibility"]) | |
| if viewer: | |
| follows_res = sb.table("follows").select("following").eq("follower", viewer).eq("status", 1).in_("following", unique_users).execute() | |
| following_set_low = {f["following"].lower() for f in follows_res.data} | |
| except: | |
| pass | |
| avatar_map = {} | |
| if unique_users: | |
| # Check cache first | |
| missing_users = [] | |
| for u in unique_users: | |
| cached = _get_cached(_avatar_cache, u) | |
| if cached is not None: | |
| avatar_map[u] = cached | |
| else: | |
| missing_users.append(u) | |
| if missing_users: | |
| try: | |
| # Batch fetch missing avatars from DB | |
| users_res = sb.table("users").select("username, avatar").in_("username", missing_users).execute() | |
| for ur in users_res.data: | |
| u_name = ur.get("username") | |
| if u_name: | |
| av = ur.get("avatar") or "" | |
| avatar_map[u_name] = av | |
| _set_cache(_avatar_cache, u_name, av) | |
| except: | |
| pass | |
| for r in rows.data: | |
| # Filtering based on blocks | |
| post_user = r["username"] | |
| post_user_low = post_user.lower() if post_user else "" | |
| station_name = r["station_name"] | |
| # If user is blocked/blocker, skip unless it's the viewer themselves | |
| if post_user_low in restricted_users_low and post_user_low != viewer_low: | |
| continue | |
| # Private account check (only for personal posts) | |
| if not station_name and post_user_low != viewer_low: | |
| visibility = visibility_map.get(post_user, 1) # Default to public if missing | |
| if visibility == 0 and post_user_low not in following_set_low: | |
| continue | |
| # If it's a station post, check station admin | |
| if station_name: | |
| admin, s_avatar = station_admin_map.get(station_name, (None, "")) | |
| if admin and admin.lower() in restricted_users_low and admin.lower() != viewer_low: | |
| continue | |
| r["avatar"] = s_avatar | |
| else: | |
| r["avatar"] = avatar_map.get(post_user, "") | |
| p = {k: str(v) for k, v in r.items()} | |
| p["timestamp"] = str(parse_db_timestamp(r["timestamp"])) | |
| p["is_liked"] = p["id"] in liked_post_ids | |
| results.append(p) | |
| return results | |
| def get_user_posts(username, viewer=""): | |
| # If blocked, return empty | |
| if viewer and engine_user.is_blocked_either_way(username, viewer): | |
| return [] | |
| sb = get_service_client() | |
| # Check target visibility | |
| if username != viewer: | |
| try: | |
| target_res = sb.table("users").select("visibility").eq("username", username).execute() | |
| if target_res.data: | |
| visibility = int(target_res.data[0]["visibility"]) | |
| if visibility == 0: | |
| # Check if viewer follows | |
| status = engine_user.get_follow_status(viewer, username) | |
| if status != 1: | |
| return [] | |
| except: | |
| pass | |
| # Case-insensitive self-check: viewer can always see their own posts | |
| is_self = False | |
| if viewer and username and viewer.lower() == username.lower(): | |
| is_self = True | |
| rows = sb.table("posts").select("id, username, station_name, content, media_path, timestamp, comments_count, likes_count").eq("username", username).order("timestamp", desc=True).execute() | |
| # Pre-fetch likes if viewer is provided | |
| liked_post_ids = [] | |
| if viewer: | |
| try: | |
| likes_res = sb.table("likes").select("post_id").eq("username", viewer).eq("isLike", 1).execute() | |
| liked_post_ids = [str(l["post_id"]) for l in likes_res.data] | |
| except: | |
| pass | |
| # Batch fetch station avatars if any posts are in stations | |
| unique_stations = list(set(r["station_name"] for r in rows.data if r.get("station_name"))) | |
| station_avatar_map = {} | |
| if unique_stations: | |
| try: | |
| stations_res = sb.table("stations").select("station_name, profile_pic").in_("station_name", unique_stations).execute() | |
| for sr in stations_res.data: | |
| s_name = sr.get("station_name") | |
| if s_name: | |
| station_avatar_map[s_name] = sr.get("profile_pic") or "" | |
| except: | |
| pass | |
| # Fetch user avatar | |
| cached_avatar = _get_cached(_avatar_cache, username) | |
| if cached_avatar is None: | |
| cached_avatar = engine_user.get_avatar(username) | |
| _set_cache(_avatar_cache, username, cached_avatar) | |
| user_avatar = cached_avatar | |
| results = [] | |
| for r in rows.data: | |
| p = {k: str(v) for k, v in r.items()} | |
| p["timestamp"] = str(parse_db_timestamp(r["timestamp"])) | |
| p["is_liked"] = p["id"] in liked_post_ids | |
| # If it's a station post, use station avatar | |
| if r.get("station_name"): | |
| p["avatar"] = station_avatar_map.get(r["station_name"], "") | |
| else: | |
| p["avatar"] = user_avatar | |
| results.append(p) | |
| return results | |
| def is_liked(post_id, username): | |
| sb = get_service_client() | |
| res = sb.table("likes").select("isLike").eq("post_id", post_id).eq("username", username).execute() | |
| if res.data: | |
| return str(res.data[0]["isLike"]) == "1" | |
| return False | |
| # ======================== | |
| # LIKES | |
| # ======================== | |
| def like_post(post_id, username): | |
| sb = get_service_client() | |
| pid = post_id | |
| rows = sb.table("likes").select("isLike").eq("post_id", pid).eq("username", username).execute() | |
| if rows.data: | |
| if str(rows.data[0]["isLike"]) == "1": | |
| return False | |
| sb.table("likes").update({"isLike": 1}).eq("post_id", pid).eq("username", username).execute() | |
| return True | |
| sb.table("likes").insert({"post_id": pid, "username": username, "isLike": 1}).execute() | |
| return True | |
| def unlike_post(post_id, username): | |
| sb = get_service_client() | |
| pid = post_id | |
| rows = sb.table("likes").select("isLike").eq("post_id", pid).eq("username", username).execute() | |
| if not rows.data: return False | |
| if str(rows.data[0]["isLike"]) == "0": | |
| return False | |
| sb.table("likes").update({"isLike": 0}).eq("post_id", pid).eq("username", username).execute() | |
| return True | |
| def modify_likes(post_id, delta): | |
| sb = get_service_client() | |
| rows = sb.table("posts").select("likes_count").eq("id", post_id).execute() | |
| if not rows.data: return -1 | |
| current = int(rows.data[0]["likes_count"]) | |
| new_count = max(0, current + delta) | |
| sb.table("posts").update({"likes_count": new_count}).eq("id", post_id).execute() | |
| # Update cache | |
| _set_cache(_post_likes_cache, post_id, new_count) | |
| return new_count | |
| # ======================== | |
| # COMMENTS | |
| # ======================== | |
| def add_comment(post_id, username, content): | |
| sb = get_service_client() | |
| # Database handles timestamp automatically via DEFAULT NOW() | |
| sb.table("comments").insert({"post_id": post_id, "username": username, "content": content}).execute() | |
| rows = sb.table("posts").select("comments_count").eq("id", post_id).execute() | |
| if rows.data: | |
| sb.table("posts").update({"comments_count": int(rows.data[0]["comments_count"]) + 1}).eq("id", post_id).execute() | |
| return True | |
| def get_comments(post_id, viewer=""): | |
| sb = get_service_client() | |
| rows = sb.table("comments").select("username, content, timestamp").eq("post_id", post_id).order("timestamp", desc=True).execute() | |
| restricted_users = {u.lower() for u in engine_user.get_blocking_relationship_usernames(viewer)} if viewer else set() | |
| results = [] | |
| for r in rows.data: | |
| if r["username"].lower() in restricted_users: | |
| continue | |
| results.append({ | |
| "username": r["username"], | |
| "content": r["content"], | |
| "timestamp": str(parse_db_timestamp(r["timestamp"])) | |
| }) | |
| return results | |
| def get_post(post_id, viewer=""): | |
| sb = get_service_client() | |
| rows = sb.table("posts").select("*").eq("id", post_id).execute() | |
| if not rows.data: | |
| return None | |
| r = rows.data[0] | |
| # Check blocks | |
| if viewer: | |
| if engine_user.is_blocked_either_way(r["username"], viewer): | |
| return None | |
| if r["station_name"]: | |
| import engine_station | |
| admin = engine_station.get_station_admin(r["station_name"]) | |
| if admin and engine_user.is_blocked_either_way(admin, viewer): | |
| return None | |
| # Check visibility: if post owner is private and viewer doesn't follow them | |
| post_owner = r["username"] | |
| is_self = viewer and post_owner and viewer.lower() == post_owner.lower() | |
| if viewer and not is_self and not r.get("station_name"): | |
| owner_res = sb.table("users").select("visibility").eq("username", post_owner).execute() | |
| if owner_res.data and int(owner_res.data[0]["visibility"]) == 0: | |
| follows_res = sb.table("follows").select("id").eq("follower", viewer).eq("following", post_owner).eq("status", 1).execute() | |
| if not follows_res.data: | |
| return {"restricted": True, "username": post_owner} | |
| p = {k: str(v) for k, v in r.items()} | |
| p["timestamp"] = str(parse_db_timestamp(r["timestamp"])) | |
| # Check if liked | |
| p["is_liked"] = False | |
| if viewer: | |
| likes_res = sb.table("likes").select("isLike").eq("post_id", post_id).eq("username", viewer).execute() | |
| if likes_res.data: | |
| p["is_liked"] = str(likes_res.data[0]["isLike"]) == "1" | |
| # Avatar resolution | |
| if r.get("station_name"): | |
| import engine_station | |
| p["avatar"] = engine_station.get_station_profile_pic(r["station_name"]) | |
| else: | |
| p["avatar"] = engine_user.get_avatar(r["username"]) | |
| return p |