"""Station module""" import time from db import get_service_client import engine_user from engineHelper import parse_db_timestamp, upload_media_to_cloudinary def _is_member(station_name, username): sb = get_service_client() res = sb.table("station_members").select("username").eq("station_name", station_name).eq("username", username).execute() return len(res.data) > 0 def create_station(station_name, admin_username, bio=""): sb = get_service_client() existing = sb.table("stations").select("station_name").eq("station_name", station_name).execute() if existing.data: return False # Create station sb.table("stations").insert({ "station_name": station_name, "admin": admin_username, "user_count": 0, "bio": bio, "hero_banner": "", "profile_pic": "" }).execute() # Add admin to members sb.table("station_members").insert({"station_name": station_name, "username": admin_username}).execute() return True def delete_station(station_name, username): sb = get_service_client() rows = sb.table("stations").select("admin").eq("station_name", station_name).eq("admin", username).execute() if not rows.data: return False # Manually handle cascading deletes for posts, members, and requests sb.table("posts").delete().eq("station_name", station_name).execute() sb.table("station_members").delete().eq("station_name", station_name).execute() sb.table("station_requests").delete().eq("station_name", station_name).execute() # Delete the station itself sb.table("stations").delete().eq("station_name", station_name).execute() return True def force_delete_station(station_name): sb = get_service_client() # Manually handle cascading deletes for posts, members, and requests sb.table("posts").delete().eq("station_name", station_name).execute() sb.table("station_members").delete().eq("station_name", station_name).execute() sb.table("station_requests").delete().eq("station_name", station_name).execute() # Delete the station itself sb.table("stations").delete().eq("station_name", station_name).execute() return True def update_station_name(old_name, new_name): sb = get_service_client() existing = sb.table("stations").select("station_name").eq("station_name", new_name).execute() if existing.data: return False sb.table("stations").update({"station_name": new_name}).eq("station_name", old_name).execute() sb.table("posts").update({"station_name": new_name}).eq("station_name", old_name).execute() sb.table("station_requests").update({"station_name": new_name}).eq("station_name", old_name).execute() return True def add_user_to_station(station_name, admin_username, user_to_add): # Check if admin and user have blocked each other if engine_user.is_blocked_either_way(admin_username, user_to_add): return False sb = get_service_client() rows = sb.table("stations").select("admin").eq("station_name", station_name).execute() if not rows.data or rows.data[0]["admin"] != admin_username: return False if _is_member(station_name, user_to_add): return True # already a member, treat as success try: sb.table("station_members").insert({"station_name": station_name, "username": user_to_add}).execute() except Exception as e: if "23505" in str(e): # duplicate key return True raise return True def remove_user_from_station(station_name, admin_username, user_to_remove): sb = get_service_client() rows = sb.table("stations").select("admin").eq("station_name", station_name).execute() if not rows.data: return False admin = rows.data[0]["admin"] if admin != admin_username and user_to_remove != admin: return False sb.table("station_members").delete().eq("station_name", station_name).eq("username", user_to_remove).execute() return True def request_to_join(station_name, username): sb = get_service_client() rows = sb.table("stations").select("admin").eq("station_name", station_name).execute() if not rows.data: return False admin = rows.data[0]["admin"] # Block check if engine_user.is_blocked_either_way(admin, username): return False if admin == username or _is_member(station_name, username): return False pending = sb.table("station_requests").select("id").eq("station_name", station_name).eq("username", username).eq("status", "pending").execute() if pending.data: return False sb.table("station_requests").insert({"station_name": station_name, "username": username, "status": "pending"}).execute() return True def check_pending_request(station_name, username): sb = get_service_client() rows = sb.table("station_requests").select("id").eq("station_name", station_name).eq("username", username).eq("status", "pending").execute() return len(rows.data) > 0 def approve_request(station_name, admin_username, user_to_approve): sb = get_service_client() station_rows = sb.table("stations").select("admin").eq("station_name", station_name).eq("admin", admin_username).execute() if not station_rows.data: return False sb.table("station_requests").update({"status": "approved"}).eq("station_name", station_name).eq("username", user_to_approve).execute() # Add to members add_user_to_station(station_name, admin_username, user_to_approve) return True def reject_request(station_name, admin_username, user_to_reject): sb = get_service_client() station_rows = sb.table("stations").select("admin").eq("station_name", station_name).eq("admin", admin_username).execute() if not station_rows.data: return False sb.table("station_requests").update({"status": "rejected"}).eq("station_name", station_name).eq("username", user_to_reject).execute() return True def get_pending_requests(station_name, admin_username): sb = get_service_client() station_rows = sb.table("stations").select("admin").eq("station_name", station_name).eq("admin", admin_username).execute() if not station_rows.data: return [] rows = sb.table("station_requests").select("id, station_name, username, timestamp").eq("station_name", station_name).eq("status", "pending").execute() usernames = [r["username"] for r in rows.data] avatar_map = {} if usernames: try: profiles = sb.table("users").select("username, avatar").in_("username", usernames).execute() for p in profiles.data: avatar_map[p["username"]] = p["avatar"] except Exception: pass return [{"id": str(r["id"]), "station_name": r["station_name"], "username": r["username"], "avatar": avatar_map.get(r["username"]), "timestamp": str(parse_db_timestamp(r["timestamp"]))} for r in rows.data] def create_station_post(station_name, username, content, media_path): sb = get_service_client() rows = sb.table("stations").select("admin").eq("station_name", station_name).execute() if not rows.data: return {} admin = rows.data[0]["admin"] # Block check if engine_user.is_blocked_either_way(admin, username): return {} if admin != username and not _is_member(station_name, username): return {} 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": station_name, "content": content, "media_path": media_path, "comments_count": 0, "likes_count": 0 }).execute() row = result.data[0] # Fetch station profile pic for branding station_avatar = "" try: s_rows = sb.table("stations").select("profile_pic").eq("station_name", station_name).execute() if s_rows.data: station_avatar = s_rows.data[0].get("profile_pic") or "" except Exception: pass return { "id": str(row["id"]), "station_name": station_name, "username": username, "content": content, "media_path": media_path, "timestamp": str(row["timestamp"]), "comments_count": "0", "likes_count": "0", "avatar": station_avatar } def delete_station_post(post_id, station_name, username): sb = get_service_client() station_rows = sb.table("stations").select("admin").eq("station_name", station_name).execute() if not station_rows.data: return False admin = station_rows.data[0]["admin"] is_admin = admin == username post_rows = sb.table("posts").select("username").eq("id", post_id).eq("station_name", station_name).execute() if not post_rows.data: return False is_author = post_rows.data[0]["username"] == username is_member = is_admin or _is_member(station_name, username) if not is_admin and not is_author: return False if not is_member and not is_author: return False sb.table("posts").delete().eq("id", post_id).eq("station_name", station_name).execute() return True def get_station_posts(station_name, viewer=""): sb = get_service_client() # Get station admin to check blocks rows_admin = sb.table("stations").select("admin, profile_pic").eq("station_name", station_name).execute() if not rows_admin.data: return [] admin = rows_admin.data[0]["admin"] station_avatar = rows_admin.data[0].get("profile_pic") or "" if viewer and engine_user.is_blocked_either_way(admin, viewer): return [] rows = sb.table("posts").select("id, station_name, username, content, media_path, timestamp, comments_count, likes_count").eq("station_name", station_name).order("timestamp", desc=True).execute() restricted_users = {u.lower() for u in engine_user.get_blocking_relationship_usernames(viewer)} if viewer else set() # 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 Exception: liked_post_ids = [] results = [] for r in rows.data: # Filter posts from blocked users within the station if r["username"].lower() in restricted_users: continue p = {k: str(v) for k, v in r.items()} p["avatar"] = station_avatar p["is_liked"] = str(r["id"]) in liked_post_ids results.append(p) return results def get_station(station_name, viewer=""): sb = get_service_client() rows = sb.table("stations").select("station_name, admin, user_count, bio, hero_banner, profile_pic").eq("station_name", station_name).execute() if not rows.data: return [] admin = rows.data[0]["admin"] if viewer and engine_user.is_blocked_either_way(admin, viewer): return [] station = {k: str(v) if v is not None else "" for k, v in rows.data[0].items()} # Fetch members with avatars members_res = sb.table("station_members").select("username").eq("station_name", station_name).execute() restricted_users = {u.lower() for u in engine_user.get_blocking_relationship_usernames(viewer)} if viewer else set() usernames = [r["username"] for r in members_res.data if r["username"].lower() not in restricted_users] avatar_map = {} if usernames: try: profiles_res = sb.table("users").select("username, avatar").in_("username", usernames).execute() for prof in profiles_res.data: avatar_map[prof["username"]] = prof["avatar"] except Exception: pass station["users"] = ",".join(usernames) station["members_info"] = [{"username": u, "avatar": avatar_map.get(u)} for u in usernames] return [station] def get_station_members(station_name, viewer=""): sb = get_service_client() rows = sb.table("station_members").select("username").eq("station_name", station_name).execute() restricted_users = {u.lower() for u in engine_user.get_blocking_relationship_usernames(viewer)} if viewer else set() return [r["username"] for r in rows.data if r["username"].lower() not in restricted_users] def get_all_stations(viewer=""): sb = get_service_client() rows = sb.table("stations").select("station_name, admin, user_count, bio, hero_banner, profile_pic").execute() if not rows.data: return [] restricted_users = {u.lower() for u in engine_user.get_blocking_relationship_usernames(viewer)} if viewer else set() # Filter stations where admin is blocked filtered_rows = [r for r in rows.data if r["admin"].lower() not in restricted_users] # Fetch all members in one query station_names = [r["station_name"] for r in filtered_rows] if not station_names: return [] all_members = sb.table("station_members").select("station_name, username").in_("station_name", station_names).execute() members_map = {} for m in all_members.data: if m["username"].lower() not in restricted_users: members_map.setdefault(m["station_name"], []).append(m["username"]) result = [] for r in filtered_rows: s = {k: str(v) if v is not None else "" for k, v in r.items()} s["users"] = ",".join(members_map.get(r["station_name"], [])) result.append(s) return result def get_station_bio(station_name): sb = get_service_client() rows = sb.table("stations").select("bio").eq("station_name", station_name).execute() return rows.data[0]["bio"] if rows.data else "" def get_station_hero_banner(station_name): sb = get_service_client() rows = sb.table("stations").select("hero_banner").eq("station_name", station_name).execute() return rows.data[0]["hero_banner"] if rows.data else "" def get_station_profile_pic(station_name): sb = get_service_client() rows = sb.table("stations").select("profile_pic").eq("station_name", station_name).execute() return rows.data[0]["profile_pic"] if rows.data else "" def get_station_admin(station_name): sb = get_service_client() rows = sb.table("stations").select("admin").eq("station_name", station_name).execute() return rows.data[0]["admin"] if rows.data else "" def update_station_bio(station_name, bio): sb = get_service_client() sb.table("stations").update({"bio": bio}).eq("station_name", station_name).execute() return True def update_station_hero_banner(station_name, hero_banner_base64): sb = get_service_client() if hero_banner_base64 and hero_banner_base64.startswith("data:"): hero_banner_base64 = upload_media_to_cloudinary(hero_banner_base64) sb.table("stations").update({"hero_banner": hero_banner_base64}).eq("station_name", station_name).execute() return True def update_station_profile_pic(station_name, profile_pic_base64): sb = get_service_client() if profile_pic_base64 and profile_pic_base64.startswith("data:"): profile_pic_base64 = upload_media_to_cloudinary(profile_pic_base64) sb.table("stations").update({"profile_pic": profile_pic_base64}).eq("station_name", station_name).execute() return True def get_bulk_station_posts(station_names, viewer=""): if not station_names: return [] sb = get_service_client() restricted_users = {u.lower() for u in engine_user.get_blocking_relationship_usernames(viewer)} if viewer else set() # Filter station names where admin is blocked stations_info = sb.table("stations").select("station_name, admin, profile_pic").in_("station_name", station_names).execute() allowed_stations = [] station_avatar_map = {} for si in stations_info.data: if si["admin"].lower() not in restricted_users: allowed_stations.append(si["station_name"]) station_avatar_map[si["station_name"]] = si["profile_pic"] or "" if not allowed_stations: return [] rows = sb.table("posts").select("id, station_name, username, content, media_path, timestamp, comments_count, likes_count").in_("station_name", allowed_stations).order("timestamp", desc=True).execute() 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 Exception: pass results = [] for r in rows.data: if r["username"].lower() in restricted_users: continue p = {k: str(v) for k, v in r.items()} p["avatar"] = station_avatar_map.get(r["station_name"], "") p["is_liked"] = str(r["id"]) in liked_post_ids results.append(p) return results def get_bulk_pending_requests(station_names, username): if not station_names: return {} sb = get_service_client() res = sb.table("station_requests").select("station_name").in_("station_name", station_names).eq("username", username).eq("status", "pending").execute() pending_map = {name: False for name in station_names} for r in res.data: pending_map[r["station_name"]] = True return pending_map def cleanup_station_memberships_on_block(user1, user2): """Remove cross-memberships when a block occurs""" sb = get_service_client() # 1. Handle stations where user1 is admin (remove user2) stations1 = sb.table("stations").select("station_name").eq("admin", user1).execute() s_names1 = [s["station_name"] for s in stations1.data] if s_names1: sb.table("station_members").delete().in_("station_name", s_names1).eq("username", user2).execute() sb.table("station_requests").update({"status": "rejected"}).in_("station_name", s_names1).eq("username", user2).eq("status", "pending").execute() # Delete blocked user's posts from these stations sb.table("posts").delete().in_("station_name", s_names1).eq("username", user2).execute() # 2. Handle stations where user2 is admin (remove user1) stations2 = sb.table("stations").select("station_name").eq("admin", user2).execute() s_names2 = [s["station_name"] for s in stations2.data] if s_names2: sb.table("station_members").delete().in_("station_name", s_names2).eq("username", user1).execute() sb.table("station_requests").update({"status": "rejected"}).in_("station_name", s_names2).eq("username", user1).eq("status", "pending").execute() # Delete blocker's posts from these stations sb.table("posts").delete().in_("station_name", s_names2).eq("username", user1).execute()