feat: implement modular architecture with blueprints for auth, chat, rooms, admin, and watchparty management

This commit is contained in:
Ben Miller 2026-06-29 22:37:01 -05:00
parent c24c4acaec
commit 1a5f55eb93
9 changed files with 2 additions and 164 deletions

View file

@ -110,8 +110,6 @@ def admin_ban_ip():
return jsonify({"error": "no ip"}), 400
banned_ips.add(ip_to_ban)
from models.database import db_add_ban
db_add_ban("ip", ip_to_ban)
user_sessions.pop(username, None)
@ -131,8 +129,6 @@ def admin_ban_username():
return jsonify({"error": "no username"}), 400
banned_usernames.add(username)
from models.database import db_add_ban
db_add_ban("username", username)
user_sessions.pop(username, None)
@ -152,8 +148,6 @@ def admin_ban_raw_ip():
return jsonify({"error": "no ip"}), 400
banned_ips.add(ip)
from models.database import db_add_ban
db_add_ban("ip", ip)
return jsonify({"banned": ip})
@ -177,8 +171,6 @@ def admin_release_user():
user_sessions.pop(username, None)
user_ips.pop(username, None)
from models.database import db_delete_user_ip
db_delete_user_ip(username)
for room in room_users:
room_users[room].pop(username, None)
@ -220,14 +212,11 @@ def admin_nuke_room():
messages.pop(room, None)
room_users.pop(room, None)
room_owners.pop(room, None)
from models.database import db_delete_room_owner, db_remove_public_room
db_delete_room_owner(room)
video_state.pop(room, None)
if room in public_chat_rooms:
public_chat_rooms.remove(room)
db_remove_public_room(room)
return jsonify({"status": "nuked", "room": room})
@ -244,12 +233,8 @@ def admin_unban():
if ban_type == "ip":
banned_ips.discard(value)
from models.database import db_remove_ban
db_remove_ban("ip", value)
elif ban_type == "username":
banned_usernames.discard(value)
from models.database import db_remove_ban
db_remove_ban("username", value)
else:
return jsonify({"error": "Invalid ban type"}), 400
@ -268,12 +253,8 @@ def admin_set_room_owner():
if owner:
room_owners[room] = owner
from models.database import db_set_room_owner
db_set_room_owner(room, owner)
else:
room_owners.pop(room, None)
from models.database import db_delete_room_owner
db_delete_room_owner(room)
return jsonify({"status": "updated", "room": room, "owner": owner})

View file

@ -44,9 +44,6 @@ def register():
}
user_ips[username] = user_ip
from models.database import db_set_user_ip
db_set_user_ip(username, user_ip)
return jsonify({"username": username, "key": user_key})
@ -65,6 +62,7 @@ def validate():
"key": key,
"last_active": datetime.now(timezone.utc).isoformat(),
}
user_ips[username] = request.remote_addr
return jsonify({"status": "valid"})
return jsonify({"error": "Invalid credentials"}), 403
@ -82,10 +80,8 @@ def logout():
if verify_user_key(username, key):
# Remove from active sessions
user_sessions.pop(username, None)
# Remove from user_ips in memory and SQLite to free up the username immediately
# Remove from user_ips in memory to free up the username immediately
user_ips.pop(username, None)
from models.database import db_delete_user_ip
db_delete_user_ip(username)
# Remove from all rooms
from models.state import room_users

View file

@ -105,8 +105,6 @@ def room_user_list(room):
room_users.setdefault(room, {})
if room not in room_owners and username not in room_users[room]:
room_owners[room] = username # First user becomes the owner
from models.database import db_set_room_owner
db_set_room_owner(room, username)
room_users[room][username] = datetime.now(timezone.utc).isoformat()
@ -159,8 +157,6 @@ def poll_room(room):
room_users.setdefault(room, {})
if room not in room_owners and username not in room_users[room]:
room_owners[room] = username
from models.database import db_set_room_owner
db_set_room_owner(room, username)
room_users[room][username] = datetime.now(timezone.utc).isoformat()

View file

@ -25,8 +25,6 @@ def get_or_create_rooms():
return jsonify({"error": "Room already exists"}), 400
public_chat_rooms.append(room_name)
from models.database import db_add_public_room
db_add_public_room(room_name)
return jsonify({"name": room_name})
active_only = request.args.get("activeOnly") == "true"

View file

@ -13,8 +13,6 @@ def watchparty_video_control(room):
# Ensure it's tracked as a public room
if room not in public_chat_rooms:
public_chat_rooms.append(room)
from models.database import db_add_public_room
db_add_public_room(room)
# Ensure video_state storage is initialized
video_state.setdefault(room, {})

View file

@ -40,9 +40,6 @@ def cleanup_loop():
):
if room in public_chat_rooms:
public_chat_rooms.remove(room)
from models.database import db_remove_public_room, db_delete_room_owner
db_remove_public_room(room)
db_delete_room_owner(room)
messages.pop(room, None)
room_users.pop(room, None)
kicked_users.pop(room, None)

View file

@ -1,125 +0,0 @@
import sqlite3
import os
DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "transient_chat.db")
def get_db_connection():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
with get_db_connection() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS bans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT,
value TEXT UNIQUE
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS room_owners (
room TEXT PRIMARY KEY,
owner TEXT
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS user_ips (
username TEXT PRIMARY KEY,
ip TEXT
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS public_rooms (
room TEXT PRIMARY KEY
)
""")
conn.commit()
def load_persisted_state(state_module):
"""Loads database state into the memory-based collections of state_module."""
init_db()
with get_db_connection() as conn:
# Load bans
for row in conn.execute("SELECT type, value FROM bans"):
if row["type"] == "ip":
state_module.banned_ips.add(row["value"])
elif row["type"] == "username":
state_module.banned_usernames.add(row["value"])
# Load room owners
for row in conn.execute("SELECT room, owner FROM room_owners"):
state_module.room_owners[row["room"]] = row["owner"]
# Load user IPs
for row in conn.execute("SELECT username, ip FROM user_ips"):
state_module.user_ips[row["username"]] = row["ip"]
# Load public rooms
for row in conn.execute("SELECT room FROM public_rooms"):
if row["room"] not in state_module.public_chat_rooms:
state_module.public_chat_rooms.append(row["room"])
def db_add_ban(ban_type, value):
try:
with get_db_connection() as conn:
conn.execute("INSERT OR IGNORE INTO bans (type, value) VALUES (?, ?)", (ban_type, value))
conn.commit()
except Exception as e:
print(f"DB Error db_add_ban: {e}")
def db_remove_ban(ban_type, value):
try:
with get_db_connection() as conn:
conn.execute("DELETE FROM bans WHERE type = ? AND value = ?", (ban_type, value))
conn.commit()
except Exception as e:
print(f"DB Error db_remove_ban: {e}")
def db_set_room_owner(room, owner):
try:
with get_db_connection() as conn:
conn.execute("INSERT OR REPLACE INTO room_owners (room, owner) VALUES (?, ?)", (room, owner))
conn.commit()
except Exception as e:
print(f"DB Error db_set_room_owner: {e}")
def db_delete_room_owner(room):
try:
with get_db_connection() as conn:
conn.execute("DELETE FROM room_owners WHERE room = ?", (room,))
conn.commit()
except Exception as e:
print(f"DB Error db_delete_room_owner: {e}")
def db_set_user_ip(username, ip):
try:
with get_db_connection() as conn:
conn.execute("INSERT OR REPLACE INTO user_ips (username, ip) VALUES (?, ?)", (username, ip))
conn.commit()
except Exception as e:
print(f"DB Error db_set_user_ip: {e}")
def db_delete_user_ip(username):
try:
with get_db_connection() as conn:
conn.execute("DELETE FROM user_ips WHERE username = ?", (username,))
conn.commit()
except Exception as e:
print(f"DB Error db_delete_user_ip: {e}")
def db_add_public_room(room):
try:
with get_db_connection() as conn:
conn.execute("INSERT OR IGNORE INTO public_rooms (room) VALUES (?)", (room,))
conn.commit()
except Exception as e:
print(f"DB Error db_add_public_room: {e}")
def db_remove_public_room(room):
try:
with get_db_connection() as conn:
conn.execute("DELETE FROM public_rooms WHERE room = ?", (room,))
conn.commit()
except Exception as e:
print(f"DB Error db_remove_public_room: {e}")

View file

@ -116,6 +116,3 @@ topical_chat_rooms = [
"Sustainable Living",
]
import sys
from models.database import load_persisted_state
load_persisted_state(sys.modules[__name__])

Binary file not shown.