Spaces:
Running
Running
| from flask import Blueprint, request, jsonify | |
| from backend.core.decorators import jwt_required, get_current_user | |
| from backend.database.db_manager import get_user_db_conn | |
| from backend.core.logger import logger | |
| from backend.core.security import encrypt_message, decrypt_message | |
| from datetime import datetime | |
| sects_bp = Blueprint("sects", __name__) | |
| def serialize_row(row): | |
| if not row: | |
| return {} | |
| d = dict(row) | |
| for k, v in d.items(): | |
| if hasattr(v, "isoformat"): | |
| d[k] = v.isoformat() | |
| return d | |
| def list_sects(): | |
| conn = get_user_db_conn() | |
| try: | |
| rows = conn.execute(""" | |
| SELECT s.id, s.name, s.slogan, s.badge, s.level, s.contribution, s.created_at, | |
| u.username as leader_name, | |
| (SELECT COUNT(*) FROM sect_members WHERE sect_id = s.id) as member_count | |
| FROM sects s | |
| LEFT JOIN users u ON s.leader_id = u.id | |
| ORDER BY s.contribution DESC, s.created_at DESC | |
| """).fetchall() | |
| user = get_current_user() | |
| user_requests = [] | |
| if user: | |
| req_rows = conn.execute("SELECT sect_id FROM sect_join_requests WHERE user_id = ? AND status = 'pending'", (user["id"],)).fetchall() | |
| user_requests = [r["sect_id"] for r in req_rows] | |
| return jsonify({ | |
| "sects": [serialize_row(r) for r in rows], | |
| "pending_requests": user_requests | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error listing sects: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def create_sect(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| name = data.get("name", "").strip() | |
| slogan = data.get("slogan", "").strip() | |
| badge = data.get("badge", "purple").strip() | |
| if not name: | |
| return jsonify({"error": "Tên tông môn không được để trống"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| # Check if user is already in a sect | |
| in_sect = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if in_sect: | |
| return jsonify({"error": "Bạn đã thuộc một tông môn khác, hãy rời tông cũ trước"}), 400 | |
| # Check if name exists | |
| exists = conn.execute("SELECT id FROM sects WHERE name = ?", (name,)).fetchone() | |
| if exists: | |
| return jsonify({"error": "Tên tông môn này đã được lập từ trước"}), 400 | |
| cursor = conn.execute( | |
| "INSERT INTO sects (name, slogan, badge, leader_id, announcement) VALUES (?, ?, ?, ?, ?)", | |
| (name, slogan, badge, user["id"], f"Tông môn {name} được thành lập. Chúc các đồng môn tu tiên đắc đạo!") | |
| ) | |
| sect_id = cursor.lastrowid | |
| conn.execute( | |
| "INSERT INTO sect_members (sect_id, user_id, role, contribution) VALUES (?, ?, 'leader', 100)", | |
| (sect_id, user["id"]) | |
| ) | |
| conn.execute( | |
| "UPDATE sects SET contribution = 100 WHERE id = ?", | |
| (sect_id,) | |
| ) | |
| # Delete any pending join requests of this user elsewhere | |
| conn.execute("DELETE FROM sect_join_requests WHERE user_id = ?", (user["id"],)) | |
| conn.commit() | |
| return jsonify({"success": True, "message": f"Sáng lập {name} tông thành công!", "sect_id": sect_id}) | |
| except Exception as e: | |
| logger.error(f"Error creating sect: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def join_sect(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| sect_id = data.get("sect_id") | |
| if not sect_id: | |
| return jsonify({"error": "Thiếu sect_id"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| # Check if user is already in a sect | |
| in_sect = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if in_sect: | |
| return jsonify({"error": "Bạn đã ở trong một tông môn khác"}), 400 | |
| # Check if sect exists | |
| sect = conn.execute("SELECT name FROM sects WHERE id = ?", (sect_id,)).fetchone() | |
| if not sect: | |
| return jsonify({"error": "Tông môn không tồn tại"}), 404 | |
| # Check if already requested | |
| req_exists = conn.execute("SELECT id FROM sect_join_requests WHERE sect_id = ? AND user_id = ?", (sect_id, user["id"])).fetchone() | |
| if req_exists: | |
| return jsonify({"error": "Bạn đã gửi yêu cầu gia nhập tông này và đang chờ duyệt"}), 400 | |
| conn.execute( | |
| "INSERT INTO sect_join_requests (sect_id, user_id, status) VALUES (?, ?, 'pending')", | |
| (sect_id, user["id"]) | |
| ) | |
| conn.commit() | |
| return jsonify({"success": True, "message": f"Đã gửi yêu cầu bái kiến gia nhập {sect['name']}! Vui lòng chờ duyệt."}) | |
| except Exception as e: | |
| logger.error(f"Error joining sect: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def leave_sect(): | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn nào"}), 400 | |
| sect_id = member["sect_id"] | |
| if member["role"] == "leader": | |
| # Leader leaving dissolves the sect | |
| conn.execute("DELETE FROM sect_chat_groups WHERE sect_id = ?", (sect_id,)) | |
| conn.execute("DELETE FROM sect_messages WHERE sect_id = ?", (sect_id,)) | |
| conn.execute("DELETE FROM sect_members WHERE sect_id = ?", (sect_id,)) | |
| conn.execute("DELETE FROM sect_join_requests WHERE sect_id = ?", (sect_id,)) | |
| conn.execute("DELETE FROM sect_books WHERE sect_id = ?", (sect_id,)) | |
| conn.execute("DELETE FROM sects WHERE id = ?", (sect_id,)) | |
| msg = "Bạn là tông chủ, rời đi đồng nghĩa với việc giải tán tông môn!" | |
| else: | |
| conn.execute("DELETE FROM sect_members WHERE user_id = ? AND sect_id = ?", (user["id"], sect_id)) | |
| msg = "Bạn đã rời khỏi tông môn thành công!" | |
| conn.commit() | |
| return jsonify({"success": True, "message": msg}) | |
| except Exception as e: | |
| logger.error(f"Error leaving sect: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def my_sect(): | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role, contribution as my_contrib FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"in_sect": False}) | |
| sect_id = member["sect_id"] | |
| sect = conn.execute(""" | |
| SELECT s.id, s.name, s.slogan, s.announcement, s.badge, s.level, s.contribution, s.created_at, | |
| u.username as leader_name, u.id as leader_id | |
| FROM sects s | |
| LEFT JOIN users u ON s.leader_id = u.id | |
| WHERE s.id = ? | |
| """, (sect_id,)).fetchone() | |
| members = conn.execute(""" | |
| SELECT sm.user_id, sm.role, sm.contribution, sm.joined_at, u.username, u.avatar | |
| FROM sect_members sm | |
| LEFT JOIN users u ON sm.user_id = u.id | |
| WHERE sm.sect_id = ? | |
| ORDER BY | |
| CASE sm.role | |
| WHEN 'leader' THEN 1 | |
| WHEN 'elder' THEN 2 | |
| ELSE 3 | |
| END, | |
| sm.contribution DESC | |
| """, (sect_id,)).fetchall() | |
| return jsonify({ | |
| "in_sect": True, | |
| "role": member["role"], | |
| "my_contribution": member["my_contrib"], | |
| "sect": serialize_row(sect), | |
| "members": [serialize_row(m) for m in members] | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error getting my sect: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def get_sect_chat_history(): | |
| user = get_current_user() | |
| chat_type = request.args.get("chat_type", "general").strip() | |
| target_id = request.args.get("target_id") | |
| group_id = request.args.get("group_id") | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = member["sect_id"] | |
| if chat_type == "direct": | |
| if not target_id: | |
| return jsonify({"error": "Thiếu target_id cho chat riêng"}), 400 | |
| rows = conn.execute(""" | |
| SELECT sm.id, sm.sender_id, sm.message, sm.created_at, sm.chat_type, sm.target_id, | |
| u.username as sender_name, u.avatar as sender_avatar | |
| FROM sect_messages sm | |
| LEFT JOIN users u ON sm.sender_id = u.id | |
| WHERE sm.sect_id = ? AND sm.chat_type = 'direct' | |
| AND ((sm.sender_id = ? AND sm.target_id = ?) OR (sm.sender_id = ? AND sm.target_id = ?)) | |
| ORDER BY sm.created_at ASC LIMIT 100 | |
| """, (sect_id, user["id"], target_id, target_id, user["id"])).fetchall() | |
| elif chat_type == "book": | |
| if not target_id: | |
| return jsonify({"error": "Thiếu target_id cho chat truyện"}), 400 | |
| rows = conn.execute(""" | |
| SELECT sm.id, sm.sender_id, sm.message, sm.created_at, sm.chat_type, sm.target_id, | |
| u.username as sender_name, u.avatar as sender_avatar | |
| FROM sect_messages sm | |
| LEFT JOIN users u ON sm.sender_id = u.id | |
| WHERE sm.sect_id = ? AND sm.chat_type = 'book' AND sm.target_id = ? | |
| ORDER BY sm.created_at ASC LIMIT 100 | |
| """, (sect_id, target_id)).fetchall() | |
| elif chat_type == "group": | |
| if not group_id: | |
| return jsonify({"error": "Thiếu group_id cho chat nhóm"}), 400 | |
| # Check group access | |
| group = conn.execute("SELECT members_csv FROM sect_chat_groups WHERE id = ? AND sect_id = ?", (group_id, sect_id)).fetchone() | |
| if not group: | |
| return jsonify({"error": "Nhóm chat không tồn tại"}), 404 | |
| members_list = [int(x) for x in group["members_csv"].split(",") if x.strip()] | |
| if user["id"] not in members_list: | |
| return jsonify({"error": "Bạn không phải thành viên của nhóm chat này"}), 403 | |
| rows = conn.execute(""" | |
| SELECT sm.id, sm.sender_id, sm.message, sm.created_at, sm.chat_type, sm.group_id, | |
| u.username as sender_name, u.avatar as sender_avatar | |
| FROM sect_messages sm | |
| LEFT JOIN users u ON sm.sender_id = u.id | |
| WHERE sm.sect_id = ? AND sm.chat_type = 'group' AND sm.group_id = ? | |
| ORDER BY sm.created_at ASC LIMIT 100 | |
| """, (sect_id, group_id)).fetchall() | |
| else: # general chat | |
| rows = conn.execute(""" | |
| SELECT sm.id, sm.sender_id, sm.message, sm.created_at, sm.chat_type, | |
| u.username as sender_name, u.avatar as sender_avatar | |
| FROM sect_messages sm | |
| LEFT JOIN users u ON sm.sender_id = u.id | |
| WHERE sm.sect_id = ? AND (sm.chat_type IS NULL OR sm.chat_type = 'general') | |
| ORDER BY sm.created_at ASC LIMIT 100 | |
| """, (sect_id,)).fetchall() | |
| messages = [] | |
| for r in rows: | |
| d = dict(r) | |
| d["message"] = decrypt_message(d.get("message", "")) | |
| for k, v in d.items(): | |
| if hasattr(v, "isoformat"): | |
| d[k] = v.isoformat() | |
| messages.append(d) | |
| return jsonify({"messages": messages}) | |
| except Exception as e: | |
| logger.error(f"Error getting sect chat: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def send_sect_chat(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| message = data.get("message", "").strip() | |
| chat_type = data.get("chat_type", "general").strip() | |
| target_id = data.get("target_id") | |
| group_id = data.get("group_id") | |
| if not message: | |
| return jsonify({"error": "Nội dung tin nhắn trống"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = member["sect_id"] | |
| # Validate target / group access | |
| if chat_type == "group" and group_id: | |
| group = conn.execute("SELECT members_csv FROM sect_chat_groups WHERE id = ? AND sect_id = ?", (group_id, sect_id)).fetchone() | |
| if not group: | |
| return jsonify({"error": "Nhóm chat không tồn tại"}), 404 | |
| members_list = [int(x) for x in group["members_csv"].split(",") if x.strip()] | |
| if user["id"] not in members_list: | |
| return jsonify({"error": "Bạn không có quyền chat trong nhóm này"}), 403 | |
| enc_msg = encrypt_message(message) | |
| cursor = conn.execute( | |
| """INSERT INTO sect_messages (sect_id, sender_id, message, chat_type, target_id, group_id) | |
| VALUES (?, ?, ?, ?, ?, ?)""", | |
| (sect_id, user["id"], enc_msg, chat_type, target_id, group_id) | |
| ) | |
| msg_id = cursor.lastrowid | |
| conn.commit() | |
| return jsonify({"success": True, "msg_id": msg_id}) | |
| except Exception as e: | |
| logger.error(f"Error sending sect chat: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def create_sect_chat_group(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| name = data.get("name", "").strip() | |
| member_ids = data.get("members", []) | |
| if not name: | |
| return jsonify({"error": "Tên nhóm chat không được để trống"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = member["sect_id"] | |
| if user["id"] not in member_ids: | |
| member_ids.append(user["id"]) | |
| valid_members = [] | |
| for uid in member_ids: | |
| is_valid = conn.execute("SELECT 1 FROM sect_members WHERE user_id = ? AND sect_id = ?", (uid, sect_id)).fetchone() | |
| if is_valid: | |
| valid_members.append(str(uid)) | |
| members_csv = ",".join(valid_members) | |
| cursor = conn.execute( | |
| "INSERT INTO sect_chat_groups (sect_id, name, creator_id, members_csv) VALUES (?, ?, ?, ?)", | |
| (sect_id, name, user["id"], members_csv) | |
| ) | |
| group_id = cursor.lastrowid | |
| conn.commit() | |
| return jsonify({"success": True, "message": f"Tạo nhóm chat '{name}' thành công!", "group_id": group_id}) | |
| except Exception as e: | |
| logger.error(f"Error creating sect chat group: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def list_sect_chat_groups(): | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = member["sect_id"] | |
| groups = conn.execute("SELECT * FROM sect_chat_groups WHERE sect_id = ?", (sect_id,)).fetchall() | |
| my_groups = [] | |
| for g in groups: | |
| members_list = [int(x) for x in g["members_csv"].split(",") if x.strip()] | |
| if user["id"] in members_list: | |
| my_groups.append(serialize_row(g)) | |
| return jsonify({"groups": my_groups}) | |
| except Exception as e: | |
| logger.error(f"Error listing sect groups: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def contribute_sect(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| amount = int(data.get("amount", 0)) | |
| if amount <= 0: | |
| return jsonify({"error": "Số lượng linh thạch cống hiến phải lớn hơn 0"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn không ở trong tông môn nào"}), 400 | |
| sect_id = member["sect_id"] | |
| # Increase user contribution | |
| conn.execute( | |
| "UPDATE sect_members SET contribution = contribution + ? WHERE user_id = ? AND sect_id = ?", | |
| (amount, user["id"], sect_id) | |
| ) | |
| # Increase sect total contribution | |
| conn.execute( | |
| "UPDATE sects SET contribution = contribution + ? WHERE id = ?", | |
| (amount, sect_id) | |
| ) | |
| # Calculate new level (1000 contribution per level) | |
| sect = conn.execute("SELECT contribution FROM sects WHERE id = ?", (sect_id,)).fetchone() | |
| new_level = max(1, int(sect["contribution"] / 1000) + 1) | |
| conn.execute("UPDATE sects SET level = ? WHERE id = ?", (new_level, sect_id)) | |
| conn.commit() | |
| return jsonify({ | |
| "success": True, | |
| "message": f"Cống hiến thành công {amount} linh thạch!", | |
| "new_level": new_level | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error contributing to sect: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def kick_member(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| target_user_id = data.get("user_id") | |
| if not target_user_id: | |
| return jsonify({"error": "Thiếu user_id thành viên cần trục xuất"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| # Check if user is leader/elder | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member or member["role"] not in ['leader', 'elder']: | |
| return jsonify({"error": "Bạn không đủ quyền hạn (Chỉ Tông chủ hoặc Trưởng lão)"}), 403 | |
| sect_id = member["sect_id"] | |
| # Check target | |
| target = conn.execute("SELECT role FROM sect_members WHERE user_id = ? AND sect_id = ?", (target_user_id, sect_id)).fetchone() | |
| if not target: | |
| return jsonify({"error": "Thành viên này không thuộc tông môn của bạn"}), 400 | |
| # Elder cannot kick leader or another elder | |
| if member["role"] == 'elder' and target["role"] in ['leader', 'elder']: | |
| return jsonify({"error": "Trưởng lão không thể trục xuất Tông chủ hoặc Trưởng lão khác"}), 403 | |
| if target["role"] == 'leader': | |
| return jsonify({"error": "Không thể trục xuất Tông chủ"}), 400 | |
| conn.execute("DELETE FROM sect_members WHERE user_id = ? AND sect_id = ?", (target_user_id, sect_id)) | |
| conn.commit() | |
| return jsonify({"success": True, "message": "Đã trục xuất thành viên ra khỏi tông môn!"}) | |
| except Exception as e: | |
| logger.error(f"Error kicking member: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| # ========================================== | |
| # ADVANCED SECTS & ALLIANCES FEATURES | |
| # ========================================== | |
| def update_announcement(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| announcement = data.get("announcement", "").strip() | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member or member["role"] not in ['leader', 'elder']: | |
| return jsonify({"error": "Chỉ Tông chủ hoặc Trưởng lão mới có quyền đổi thông cáo"}), 403 | |
| conn.execute("UPDATE sects SET announcement = ? WHERE id = ?", (announcement, member["sect_id"])) | |
| conn.commit() | |
| return jsonify({"success": True, "message": "Cập nhật thông cáo tông môn thành công!"}) | |
| except Exception as e: | |
| logger.error(f"Error updating announcement: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def promote_member(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| target_user_id = data.get("user_id") | |
| new_role = data.get("role", "member").lower() # 'elder' or 'member' | |
| if new_role not in ['elder', 'member']: | |
| return jsonify({"error": "Chức vị không hợp lệ"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member or member["role"] != 'leader': | |
| return jsonify({"error": "Chỉ có Tông chủ mới được thăng/giáng chức đệ tử"}), 403 | |
| sect_id = member["sect_id"] | |
| target = conn.execute("SELECT role FROM sect_members WHERE user_id = ? AND sect_id = ?", (target_user_id, sect_id)).fetchone() | |
| if not target: | |
| return jsonify({"error": "Thành viên này không thuộc tông môn của bạn"}), 400 | |
| if target["role"] == 'leader': | |
| return jsonify({"error": "Không thể thay đổi chức vị của Tông chủ"}), 400 | |
| conn.execute("UPDATE sect_members SET role = ? WHERE user_id = ? AND sect_id = ?", (new_role, target_user_id, sect_id)) | |
| conn.commit() | |
| role_name = "Trưởng Lão" if new_role == 'elder' else "Môn đồ" | |
| return jsonify({"success": True, "message": f"Đã thăng/giáng chức thành viên thành {role_name}!"}) | |
| except Exception as e: | |
| logger.error(f"Error promoting member: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def list_join_requests(): | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member or member["role"] not in ['leader', 'elder']: | |
| return jsonify({"error": "Chỉ Tông chủ hoặc Trưởng lão mới xem được danh sách yêu cầu"}), 403 | |
| rows = conn.execute(""" | |
| SELECT r.id, r.user_id, r.status, r.created_at, u.username, u.avatar | |
| FROM sect_join_requests r | |
| LEFT JOIN users u ON r.user_id = u.id | |
| WHERE r.sect_id = ? AND r.status = 'pending' | |
| ORDER BY r.created_at DESC | |
| """, (member["sect_id"],)).fetchall() | |
| return jsonify({"requests": [serialize_row(r) for r in rows]}) | |
| except Exception as e: | |
| logger.error(f"Error listing requests: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def respond_join_request(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| req_id = data.get("request_id") | |
| action = data.get("action", "").lower() # 'approve' or 'reject' | |
| if not req_id or action not in ['approve', 'reject']: | |
| return jsonify({"error": "Dữ liệu phản hồi không hợp lệ"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member or member["role"] not in ['leader', 'elder']: | |
| return jsonify({"error": "Chỉ Tông chủ hoặc Trưởng lão mới được phê duyệt"}), 403 | |
| sect_id = member["sect_id"] | |
| req = conn.execute("SELECT user_id FROM sect_join_requests WHERE id = ? AND sect_id = ?", (req_id, sect_id)).fetchone() | |
| if not req: | |
| return jsonify({"error": "Yêu cầu gia nhập không tồn tại hoặc đã được xử lý"}), 404 | |
| target_user_id = req["user_id"] | |
| if action == 'approve': | |
| # Check if target is already in a sect | |
| already_in = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (target_user_id,)).fetchone() | |
| if already_in: | |
| conn.execute("DELETE FROM sect_join_requests WHERE id = ?", (req_id,)) | |
| conn.commit() | |
| return jsonify({"error": "Người dùng này đã gia nhập một tông môn khác, yêu cầu tự động hủy."}), 400 | |
| # Add member | |
| conn.execute( | |
| "INSERT INTO sect_members (sect_id, user_id, role, contribution) VALUES (?, ?, 'member', 0)", | |
| (sect_id, target_user_id) | |
| ) | |
| # Delete target requests from all other sects | |
| conn.execute("DELETE FROM sect_join_requests WHERE user_id = ?", (target_user_id,)) | |
| msg = "Đã phê duyệt gia nhập tông môn!" | |
| else: | |
| # Reject | |
| conn.execute("DELETE FROM sect_join_requests WHERE id = ?", (req_id,)) | |
| msg = "Đã từ chối yêu cầu gia nhập!" | |
| conn.commit() | |
| return jsonify({"success": True, "message": msg}) | |
| except Exception as e: | |
| logger.error(f"Error responding join request: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| # ========================================== | |
| # SECT LIBRARY (BOOK SHELF SHARING) | |
| # ========================================== | |
| def list_sect_library(): | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = member["sect_id"] | |
| # We need to join with the main books database to fetch book title and cover | |
| # Since SQLite can ATTACH databases, or we can fetch metadata, we'll fetch from sect_books | |
| # and query titles from books database. Let's do it cleanly: | |
| rows = conn.execute(""" | |
| SELECT sb.id, sb.book_id, sb.added_at, u.username as added_by_name | |
| FROM sect_books sb | |
| LEFT JOIN users u ON sb.added_by = u.id | |
| WHERE sb.sect_id = ? | |
| ORDER BY sb.added_at DESC | |
| """, (sect_id,)).fetchall() | |
| from backend.database.db_manager import get_db | |
| main_conn = get_db() | |
| book_list = [] | |
| for r in rows: | |
| book_row = main_conn.execute(""" | |
| SELECT id, title, title_vietphrase, title_hanviet, author, cover, description | |
| FROM books WHERE id = ? | |
| """, (r["book_id"],)).fetchone() | |
| if book_row: | |
| b = serialize_row(book_row) | |
| b["added_by_name"] = r["added_by_name"] | |
| b["added_at"] = r["added_at"].isoformat() if hasattr(r["added_at"], "isoformat") else r["added_at"] | |
| book_list.append(b) | |
| return jsonify({"books": book_list}) | |
| except Exception as e: | |
| logger.error(f"Error listing sect library: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def add_to_sect_library(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| book_id = data.get("book_id") | |
| if not book_id: | |
| return jsonify({"error": "Thiếu book_id truyện đóng góp"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn không thuộc tông môn nào"}), 400 | |
| sect_id = member["sect_id"] | |
| # Check if already added | |
| exists = conn.execute("SELECT id FROM sect_books WHERE sect_id = ? AND book_id = ?", (sect_id, book_id)).fetchone() | |
| if exists: | |
| return jsonify({"error": "Truyện này đã được đệ tử khác đóng góp vào thư viện tông môn từ trước"}), 400 | |
| conn.execute( | |
| "INSERT INTO sect_books (sect_id, book_id, added_by) VALUES (?, ?, ?)", | |
| (sect_id, book_id, user["id"]) | |
| ) | |
| # Reward user 20 contribution points for sharing a book | |
| conn.execute( | |
| "UPDATE sect_members SET contribution = contribution + 20 WHERE user_id = ? AND sect_id = ?", | |
| (user["id"], sect_id) | |
| ) | |
| # Increase sect total contribution | |
| conn.execute( | |
| "UPDATE sects SET contribution = contribution + 20 WHERE id = ?", | |
| (sect_id,) | |
| ) | |
| conn.commit() | |
| return jsonify({"success": True, "message": "Chia sẻ truyện vào Thư viện Tông môn thành công! Nhận 20 điểm cống hiến."}) | |
| except Exception as e: | |
| logger.error(f"Error adding to sect library: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def remove_from_sect_library(): | |
| user = get_current_user() | |
| data = request.json or {} | |
| book_id = data.get("book_id") | |
| if not book_id: | |
| return jsonify({"error": "Thiếu book_id truyện cần gỡ"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute("SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],)).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn không thuộc tông môn nào"}), 400 | |
| sect_id = member["sect_id"] | |
| # Check who added it | |
| book = conn.execute("SELECT added_by FROM sect_books WHERE sect_id = ? AND book_id = ?", (sect_id, book_id)).fetchone() | |
| if not book: | |
| return jsonify({"error": "Truyện không có trong thư viện tông môn"}), 404 | |
| # Only leader, elder, or the original adder can remove | |
| if member["role"] not in ['leader', 'elder'] and book["added_by"] != user["id"]: | |
| return jsonify({"error": "Bạn không có quyền gỡ truyện này (Chỉ Tông chủ, Trưởng lão hoặc người đóng góp)"}), 403 | |
| conn.execute("DELETE FROM sect_books WHERE sect_id = ? AND book_id = ?", (sect_id, book_id)) | |
| conn.commit() | |
| return jsonify({"success": True, "message": "Đã gỡ truyện khỏi thư viện Tông môn!"}) | |
| except Exception as e: | |
| logger.error(f"Error removing from sect library: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| # ========================================== | |
| # SECT SEARCH & PROFILE BY ID | |
| # ========================================== | |
| ROLE_ORDER = { | |
| "leader": 1, | |
| "vice_leader": 2, | |
| "elder": 3, | |
| "inner_disciple": 4, | |
| "member": 5, | |
| } | |
| ROLE_LABELS = { | |
| "leader": "Tông chủ", | |
| "vice_leader": "Phó Tông chủ", | |
| "elder": "Trưởng lão", | |
| "inner_disciple": "Nội môn đệ tử", | |
| "member": "Ngoại môn đệ tử", | |
| } | |
| VALID_ROLES = list(ROLE_ORDER.keys()) | |
| def search_sects(): | |
| """Search sects by name or slogan. No auth required.""" | |
| q = request.args.get("q", "").strip() | |
| page = int(request.args.get("page", 1)) | |
| per_page = int(request.args.get("per_page", 20)) | |
| offset = (page - 1) * per_page | |
| conn = get_user_db_conn() | |
| try: | |
| if q: | |
| rows = conn.execute(""" | |
| SELECT s.id, s.name, s.slogan, s.badge, s.level, s.contribution, s.created_at, | |
| u.username as leader_name, | |
| (SELECT COUNT(*) FROM sect_members WHERE sect_id = s.id) as member_count | |
| FROM sects s | |
| LEFT JOIN users u ON s.leader_id = u.id | |
| WHERE s.name LIKE ? OR s.slogan LIKE ? | |
| ORDER BY s.contribution DESC, s.created_at DESC | |
| LIMIT ? OFFSET ? | |
| """, (f"%{q}%", f"%{q}%", per_page, offset)).fetchall() | |
| total = conn.execute( | |
| "SELECT COUNT(*) as cnt FROM sects WHERE name LIKE ? OR slogan LIKE ?", | |
| (f"%{q}%", f"%{q}%") | |
| ).fetchone()["cnt"] | |
| else: | |
| rows = conn.execute(""" | |
| SELECT s.id, s.name, s.slogan, s.badge, s.level, s.contribution, s.created_at, | |
| u.username as leader_name, | |
| (SELECT COUNT(*) FROM sect_members WHERE sect_id = s.id) as member_count | |
| FROM sects s | |
| LEFT JOIN users u ON s.leader_id = u.id | |
| ORDER BY s.contribution DESC, s.created_at DESC | |
| LIMIT ? OFFSET ? | |
| """, (per_page, offset)).fetchall() | |
| total = conn.execute("SELECT COUNT(*) as cnt FROM sects").fetchone()["cnt"] | |
| return jsonify({ | |
| "sects": [serialize_row(r) for r in rows], | |
| "total": total, | |
| "page": page, | |
| "per_page": per_page, | |
| "total_pages": max(1, (total + per_page - 1) // per_page) | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error searching sects: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def get_sect_by_id(sect_id): | |
| """View full sect profile by ID. No auth required for basic info.""" | |
| conn = get_user_db_conn() | |
| try: | |
| sect = conn.execute(""" | |
| SELECT s.id, s.name, s.slogan, s.announcement, s.badge, s.level, s.contribution, s.created_at, | |
| u.username as leader_name, u.id as leader_id, u.avatar as leader_avatar | |
| FROM sects s | |
| LEFT JOIN users u ON s.leader_id = u.id | |
| WHERE s.id = ? | |
| """, (sect_id,)).fetchone() | |
| if not sect: | |
| return jsonify({"error": "Tông môn không tồn tại"}), 404 | |
| members = conn.execute(""" | |
| SELECT sm.user_id, sm.role, sm.contribution, sm.joined_at, | |
| u.username, u.avatar, | |
| COALESCE(sm.role, 'member') as role | |
| FROM sect_members sm | |
| LEFT JOIN users u ON sm.user_id = u.id | |
| WHERE sm.sect_id = ? | |
| ORDER BY | |
| CASE sm.role | |
| WHEN 'leader' THEN 1 | |
| WHEN 'vice_leader' THEN 2 | |
| WHEN 'elder' THEN 3 | |
| WHEN 'inner_disciple' THEN 4 | |
| ELSE 5 | |
| END, | |
| sm.contribution DESC | |
| """, (sect_id,)).fetchall() | |
| # Enrich members with Vietnamese role labels | |
| member_list = [] | |
| for m in members: | |
| md = serialize_row(m) | |
| md["role_label"] = ROLE_LABELS.get(m["role"], "Môn đồ") | |
| member_list.append(md) | |
| # Count by role | |
| role_counts = {} | |
| for m in member_list: | |
| r = m["role"] | |
| role_counts[r] = role_counts.get(r, 0) + 1 | |
| return jsonify({ | |
| "sect": serialize_row(sect), | |
| "members": member_list, | |
| "member_count": len(member_list), | |
| "role_counts": role_counts, | |
| "role_labels": ROLE_LABELS, | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error fetching sect {sect_id}: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| # ========================================== | |
| # FULL RANK PROMOTION SYSTEM (5 Ranks) | |
| # ========================================== | |
| def promote_member_rank(): | |
| """ | |
| Full 5-rank promotion system: | |
| leader > vice_leader > elder > inner_disciple > member | |
| Only leader can set: vice_leader, elder, inner_disciple, member | |
| vice_leader can set: elder, inner_disciple, member | |
| elder can set: inner_disciple, member | |
| """ | |
| user = get_current_user() | |
| data = request.json or {} | |
| target_user_id = data.get("user_id") | |
| new_role = data.get("role", "").lower() | |
| if new_role not in VALID_ROLES or new_role == "leader": | |
| return jsonify({ | |
| "error": f"Chức vị không hợp lệ. Các chức vị có thể gán: {', '.join(VALID_ROLES[1:])}", | |
| "valid_roles": VALID_ROLES[1:], | |
| "role_labels": ROLE_LABELS, | |
| }), 400 | |
| if not target_user_id: | |
| return jsonify({"error": "Thiếu user_id"}), 400 | |
| conn = get_user_db_conn() | |
| try: | |
| caller = conn.execute( | |
| "SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],) | |
| ).fetchone() | |
| if not caller: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = caller["sect_id"] | |
| caller_role = caller["role"] | |
| caller_order = ROLE_ORDER.get(caller_role, 99) | |
| new_role_order = ROLE_ORDER.get(new_role, 99) | |
| # Permission matrix: caller must outrank new_role | |
| if caller_order >= new_role_order: | |
| return jsonify({ | |
| "error": f"Bạn ({ROLE_LABELS.get(caller_role)}) không đủ quyền gán chức vị {ROLE_LABELS.get(new_role)}" | |
| }), 403 | |
| target = conn.execute( | |
| "SELECT role FROM sect_members WHERE user_id = ? AND sect_id = ?", | |
| (target_user_id, sect_id) | |
| ).fetchone() | |
| if not target: | |
| return jsonify({"error": "Thành viên không thuộc tông môn của bạn"}), 404 | |
| if target["role"] == "leader": | |
| return jsonify({"error": "Không thể thay đổi chức vị của Tông chủ"}), 400 | |
| target_order = ROLE_ORDER.get(target["role"], 99) | |
| if caller_order >= target_order: | |
| return jsonify({ | |
| "error": f"Bạn ({ROLE_LABELS.get(caller_role)}) không thể thay đổi chức vị của người ngang hoặc cao hơn bạn" | |
| }), 403 | |
| conn.execute( | |
| "UPDATE sect_members SET role = ? WHERE user_id = ? AND sect_id = ?", | |
| (new_role, target_user_id, sect_id) | |
| ) | |
| conn.commit() | |
| return jsonify({ | |
| "success": True, | |
| "message": f"Đã gán chức vị {ROLE_LABELS.get(new_role)} thành công!", | |
| "new_role": new_role, | |
| "new_role_label": ROLE_LABELS.get(new_role), | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error promoting rank: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def list_sect_members(): | |
| """List all members of current user's sect with full role info.""" | |
| user = get_current_user() | |
| role_filter = request.args.get("role") | |
| conn = get_user_db_conn() | |
| try: | |
| member = conn.execute( | |
| "SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],) | |
| ).fetchone() | |
| if not member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = member["sect_id"] | |
| if role_filter and role_filter in VALID_ROLES: | |
| rows = conn.execute(""" | |
| SELECT sm.user_id, sm.role, sm.contribution, sm.joined_at, | |
| u.username, u.avatar | |
| FROM sect_members sm | |
| LEFT JOIN users u ON sm.user_id = u.id | |
| WHERE sm.sect_id = ? AND sm.role = ? | |
| ORDER BY sm.contribution DESC | |
| """, (sect_id, role_filter)).fetchall() | |
| else: | |
| rows = conn.execute(""" | |
| SELECT sm.user_id, sm.role, sm.contribution, sm.joined_at, | |
| u.username, u.avatar | |
| FROM sect_members sm | |
| LEFT JOIN users u ON sm.user_id = u.id | |
| WHERE sm.sect_id = ? | |
| ORDER BY | |
| CASE sm.role | |
| WHEN 'leader' THEN 1 | |
| WHEN 'vice_leader' THEN 2 | |
| WHEN 'elder' THEN 3 | |
| WHEN 'inner_disciple' THEN 4 | |
| ELSE 5 | |
| END, | |
| sm.contribution DESC | |
| """, (sect_id,)).fetchall() | |
| member_list = [] | |
| for r in rows: | |
| rd = serialize_row(r) | |
| rd["role_label"] = ROLE_LABELS.get(r["role"], "Môn đồ") | |
| member_list.append(rd) | |
| return jsonify({ | |
| "members": member_list, | |
| "total": len(member_list), | |
| "role_labels": ROLE_LABELS, | |
| "valid_roles": VALID_ROLES, | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error listing members: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| # ========================================== | |
| # SECT CHAT GROUPS – MANAGE MEMBERS | |
| # ========================================== | |
| def add_to_chat_group(group_id): | |
| """Add member(s) to a sub-group chat.""" | |
| user = get_current_user() | |
| data = request.json or {} | |
| new_member_ids = data.get("user_ids", []) | |
| conn = get_user_db_conn() | |
| try: | |
| my_member = conn.execute( | |
| "SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],) | |
| ).fetchone() | |
| if not my_member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = my_member["sect_id"] | |
| group = conn.execute( | |
| "SELECT id, creator_id, members_csv FROM sect_chat_groups WHERE id = ? AND sect_id = ?", | |
| (group_id, sect_id) | |
| ).fetchone() | |
| if not group: | |
| return jsonify({"error": "Nhóm chat không tồn tại"}), 404 | |
| # Only group creator, leader, vice_leader, elder can add | |
| if group["creator_id"] != user["id"] and my_member["role"] not in ["leader", "vice_leader", "elder"]: | |
| return jsonify({"error": "Chỉ người tạo nhóm hoặc Ban quản lý mới có thể thêm thành viên"}), 403 | |
| current_members = [int(x) for x in group["members_csv"].split(",") if x.strip()] | |
| added = [] | |
| for uid in new_member_ids: | |
| uid = int(uid) | |
| if uid not in current_members: | |
| # Check uid is in sect | |
| in_sect = conn.execute( | |
| "SELECT 1 FROM sect_members WHERE user_id = ? AND sect_id = ?", (uid, sect_id) | |
| ).fetchone() | |
| if in_sect: | |
| current_members.append(uid) | |
| added.append(uid) | |
| new_csv = ",".join(str(x) for x in current_members) | |
| conn.execute( | |
| "UPDATE sect_chat_groups SET members_csv = ? WHERE id = ?", | |
| (new_csv, group_id) | |
| ) | |
| conn.commit() | |
| return jsonify({"success": True, "added": added, "members": current_members}) | |
| except Exception as e: | |
| logger.error(f"Error adding to chat group: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def remove_from_chat_group(group_id): | |
| """Remove a member from a sub-group chat.""" | |
| user = get_current_user() | |
| data = request.json or {} | |
| target_id = data.get("user_id") | |
| conn = get_user_db_conn() | |
| try: | |
| my_member = conn.execute( | |
| "SELECT sect_id, role FROM sect_members WHERE user_id = ?", (user["id"],) | |
| ).fetchone() | |
| if not my_member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = my_member["sect_id"] | |
| group = conn.execute( | |
| "SELECT id, creator_id, members_csv FROM sect_chat_groups WHERE id = ? AND sect_id = ?", | |
| (group_id, sect_id) | |
| ).fetchone() | |
| if not group: | |
| return jsonify({"error": "Nhóm chat không tồn tại"}), 404 | |
| if group["creator_id"] != user["id"] and my_member["role"] not in ["leader", "vice_leader", "elder"]: | |
| return jsonify({"error": "Không đủ quyền xóa thành viên khỏi nhóm chat"}), 403 | |
| current_members = [int(x) for x in group["members_csv"].split(",") if x.strip()] | |
| if int(target_id) not in current_members: | |
| return jsonify({"error": "Thành viên không có trong nhóm chat này"}), 400 | |
| current_members.remove(int(target_id)) | |
| new_csv = ",".join(str(x) for x in current_members) | |
| conn.execute("UPDATE sect_chat_groups SET members_csv = ? WHERE id = ?", (new_csv, group_id)) | |
| conn.commit() | |
| return jsonify({"success": True, "members": current_members}) | |
| except Exception as e: | |
| logger.error(f"Error removing from chat group: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def get_chat_group_info(group_id): | |
| """Get info and member list of a sub-group chat.""" | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| my_member = conn.execute( | |
| "SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],) | |
| ).fetchone() | |
| if not my_member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = my_member["sect_id"] | |
| group = conn.execute( | |
| "SELECT * FROM sect_chat_groups WHERE id = ? AND sect_id = ?", | |
| (group_id, sect_id) | |
| ).fetchone() | |
| if not group: | |
| return jsonify({"error": "Nhóm chat không tồn tại"}), 404 | |
| member_ids = [int(x) for x in group["members_csv"].split(",") if x.strip()] | |
| if user["id"] not in member_ids: | |
| return jsonify({"error": "Bạn không phải thành viên của nhóm chat này"}), 403 | |
| # Fetch member details | |
| members = [] | |
| for uid in member_ids: | |
| row = conn.execute(""" | |
| SELECT sm.user_id, sm.role, sm.contribution, u.username, u.avatar | |
| FROM sect_members sm | |
| LEFT JOIN users u ON sm.user_id = u.id | |
| WHERE sm.user_id = ? AND sm.sect_id = ? | |
| """, (uid, sect_id)).fetchone() | |
| if row: | |
| rd = serialize_row(row) | |
| rd["role_label"] = ROLE_LABELS.get(row["role"], "Môn đồ") | |
| members.append(rd) | |
| return jsonify({ | |
| "group": serialize_row(group), | |
| "members": members, | |
| "member_count": len(members), | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error fetching chat group: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |
| def list_all_sect_chat_groups(): | |
| """List all sub-group chats the current user is part of in their sect.""" | |
| user = get_current_user() | |
| conn = get_user_db_conn() | |
| try: | |
| my_member = conn.execute( | |
| "SELECT sect_id FROM sect_members WHERE user_id = ?", (user["id"],) | |
| ).fetchone() | |
| if not my_member: | |
| return jsonify({"error": "Bạn chưa gia nhập tông môn"}), 403 | |
| sect_id = my_member["sect_id"] | |
| groups = conn.execute( | |
| "SELECT id, name, creator_id, members_csv, created_at FROM sect_chat_groups WHERE sect_id = ?", | |
| (sect_id,) | |
| ).fetchall() | |
| result = [] | |
| for g in groups: | |
| member_ids = [int(x) for x in g["members_csv"].split(",") if x.strip()] | |
| if user["id"] in member_ids: | |
| gd = serialize_row(g) | |
| gd["member_count"] = len(member_ids) | |
| result.append(gd) | |
| return jsonify({"groups": result}) | |
| except Exception as e: | |
| logger.error(f"Error listing chat groups: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| finally: | |
| conn.close() | |