backend / engine_station.py
muhammadpriv001's picture
Fix database counts and add cache-control headers
13f4235
Raw
History Blame Contribute Delete
18.7 kB
"""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()