"""Developer routes and panel.""" import os import signal import random import string from flask import Blueprint, render_template, jsonify, request, current_app from database import get_db from decorators import token_required, role_required dev_bp = Blueprint('dev', __name__) def get_socketio(): """Get SocketIO instance from current app context.""" return current_app.extensions.get('socketio') def generate_invite_code(custom=None): """Generate a random invite code or use custom one.""" if custom: return custom.upper() groups = [''.join(random.choices(string.ascii_uppercase, k=size)) for size in [3, 3, 5]] return '-'.join(groups) @dev_bp.route('/api/dev/system-stats', methods=['POST']) @token_required @role_required('Dev') def dev_system_stats(): """Get system statistics (dev only).""" db_path = current_app.config['DATABASE'] size = os.path.getsize(db_path) if os.path.exists(db_path) else 0 connected_clients = current_app.extensions.get('connected_clients', set()) return jsonify({ 'db_size_mb': round(size / (1024 * 1024), 2), 'active_users': len(connected_clients), 'uptime': 'N/A', 'message_count': get_db(current_app).execute('SELECT COUNT(*) FROM messages').fetchone()[0] }) @dev_bp.route('/api/dev/list-codes', methods=['GET']) @token_required @role_required('Dev') def dev_list_codes(): """List unused registration codes (dev only).""" return jsonify([{'code': c['code']} for c in get_db(current_app).execute( 'SELECT code FROM registration_codes WHERE is_used = 0 ORDER BY id DESC' ).fetchall()]) @dev_bp.route('/api/dev/generate-code', methods=['POST']) @token_required @role_required('Dev') def dev_generate_code(): """Generate registration codes (dev only).""" data = request.json or {} custom = data.get('custom') db = get_db(current_app) if custom: code = generate_invite_code(custom) db.execute('INSERT INTO registration_codes (code) VALUES (?)', (code,)) res = [{'code': code}] else: res = [] for _ in range(data.get('num', 10)): code = generate_invite_code() db.execute('INSERT INTO registration_codes (code) VALUES (?)', (code,)) res.append({'code': code}) db.commit() return jsonify(res) @dev_bp.route('/api/dev/global-broadcast', methods=['POST']) @token_required @role_required('Dev') def dev_global_broadcast(): """Send a system message to all users and channels (dev only).""" msg = request.json.get('message') db = get_db(current_app) for row in db.execute('SELECT id FROM channels').fetchall(): db.execute('INSERT INTO messages (sender_id, channel_id, content, is_global) VALUES (-1, ?, ?, 0)', (row['id'], msg)) for row in db.execute('SELECT id FROM users').fetchall(): db.execute('INSERT INTO messages (sender_id, receiver_id, content, is_global) VALUES (-1, ?, ?, 0)', (row['id'], msg)) db.commit() socketio = get_socketio() if socketio: socketio.emit('system_notification', {'message': msg, 'type': 'global_broadcast'}) return jsonify({'status': 'broadcasted'}) @dev_bp.route('/api/dev/restart', methods=['POST']) @token_required @role_required('Dev') def dev_restart(): """Trigger application restart using signal (dev only). This sends SIGHUP to the current process, which should be caught by the process manager (e.g., Gunicorn) to gracefully restart the application. Requires the application to run under a process manager that respects signals. """ try: # Send SIGHUP signal to current process for graceful restart # The process manager (e.g., Gunicorn) should handle this signal os.kill(os.getpid(), signal.SIGHUP) return jsonify({'status': 'restart signal sent'}), 200 except (OSError, RuntimeError) as e: return jsonify({'error': f'Failed to send restart signal: {str(e)}'}), 500 @dev_bp.route('/api/dev/broadcast', methods=['POST']) @token_required @role_required('Dev') def dev_broadcast(): """Send a system notification to all connected clients (dev only).""" socketio = get_socketio() if socketio: socketio.emit('system_notification', {'message': request.json.get('message')}) return jsonify({'status': 'broadcasted'}) @dev_bp.route('/imdadev') @token_required @role_required('Dev') def imdadev(): """Dev panel page.""" return render_template('dev.html')