import os import threading from flask import Flask, jsonify, request from .db import DB, verify_tables_once def _debug_enabled() -> bool: """Return True iff CM_DEBUG env var is set to a truthy value. Truthy: '1', 'true', 'yes' (case-insensitive, whitespace-trimmed). Anything else, including unset, is False. Default-off so the Werkzeug debugger is never reachable in production containers. """ return os.getenv("CM_DEBUG", "false").strip().lower() in ("1", "true", "yes") class CM_API: def __init__(self): self.app = Flask(__name__) # No CORS middleware: api-server is internal-only (no host port # in prod compose, per C5). Browsers can't reach it directly, # and server-side fetches from the web service don't trigger # CORS. Removing flask_cors removes a permissive '*' origin # default that becomes an attack surface if a host port is ever # accidentally re-exposed. self._register_routes() def _get_database_connection(self): """Return a DB handle backed by the shared connection pool. DB() is now a near-zero-cost handle (it just touches the cached process-wide pool); each query()/execute() rents a connection and returns it. There's nothing to clean up explicitly. """ try: return DB() except Exception as e: print(f"Database connection failed: {e}") return None def _register_routes(self): # Account routes self.app.route('/acc/', methods=['GET'])(self.get_account) self.app.route('/acc/', methods=['GET'])(self.get_account) # User routes self.app.route('/user/', methods=['GET'])(self.get_user) self.app.route('/user/', methods=['GET'])(self.get_user) # Update routes self.app.route('/update-acc-data', methods=['POST'])(self.update_acc_data) self.app.route('/update-user-data', methods=['POST'])(self.update_user_data) # Delete routes self.app.route('/delete-acc-data', methods=['POST'])(self.delete_acc_data) self.app.route('/delete-user-data', methods=['POST'])(self.delete_user_data) # Create routes (manual operator input) self.app.route('/create-acc-data', methods=['POST'])(self.create_acc_data) self.app.route('/create-user-data', methods=['POST'])(self.create_user_data) def _check_database_available(self): db = self._get_database_connection() if db is None: return False, None, ("Database not available", 500) return True, db, None def _handle_error(self, error, message="An error occurred"): print(f"Error: {error}") return message, 500 def get_account(self, username=None): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: if username: query = "SELECT username, password, status, link FROM acc WHERE username = %s" query_params = [username] else: query = "SELECT username, password, status, link FROM acc" query_params = [] results = db.query(query, query_params) return jsonify(results) except Exception as error: return self._handle_error(error, "Not Found"), 404 def get_user(self, username=None): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: if username: query = "SELECT f_username, f_password, t_username, t_password FROM user WHERE f_username = %s" query_params = [username] else: query = "SELECT f_username, f_password, t_username, t_password, last_update_time FROM user" query_params = [] results = db.query(query, query_params) return jsonify(results) except Exception as error: return self._handle_error(error, "Not Found"), 404 def update_acc_data(self): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: data = request.get_json() username = data.get('username') password = data.get('password') status = data.get('status') link = data.get('link') if not username: return jsonify({"error": "Username is required"}) result = db.execute( "UPDATE acc SET password = %s, status = %s, link = %s WHERE username = %s", [password, status, link, username] ) if result: return jsonify("Data updated successfully") else: return jsonify("Error updating data") except Exception as error: return self._handle_error(error, "Error updating data"), 500 def update_user_data(self): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: data = request.get_json() f_username = data.get('f_username') f_password = data.get('f_password') t_username = data.get('t_username') t_password = data.get('t_password') if not f_username: return jsonify({"error": "f_username is required"}) result = db.execute( "UPDATE user SET f_password = %s, t_password = %s, t_username = %s, last_update_time = CURRENT_TIMESTAMP WHERE f_username = %s", [f_password, t_password, t_username, f_username] ) if result: return jsonify("Data updated successfully") else: return jsonify("Error updating data") except Exception as error: return self._handle_error(error, "Error updating data") def delete_acc_data(self): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: data = request.get_json() or {} username = data.get('username') if not username: return jsonify({"error": "Username is required"}), 400 result = db.execute( "DELETE FROM acc WHERE username = %s", [username] ) if result: return jsonify({"deleted": username}) return jsonify({"error": "Failed to delete account"}), 500 except Exception as error: return self._handle_error(error, "Error deleting account"), 500 def delete_user_data(self): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: data = request.get_json() or {} f_username = data.get('f_username') if not f_username: return jsonify({"error": "f_username is required"}), 400 result = db.execute( "DELETE FROM user WHERE f_username = %s", [f_username] ) if result: return jsonify({"deleted": f_username}) return jsonify({"error": "Failed to delete user"}), 500 except Exception as error: return self._handle_error(error, "Error deleting user"), 500 def create_acc_data(self): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: data = request.get_json() or {} username = (data.get('username') or '').strip() password = data.get('password') or '' status = data.get('status') or '' link = data.get('link') or '' if not username or not password: return jsonify({"error": "Username and password are required"}), 400 result = db.execute( "INSERT INTO acc (username, password, status, link) VALUES (%s, %s, %s, %s)", [username, password, status, link] ) if result: return jsonify({"created": username}) return jsonify({"error": "Failed to create account"}), 500 except Exception as error: return self._handle_error(error, "Error creating account"), 500 def create_user_data(self): is_available, db, error_response = self._check_database_available() if not is_available: return error_response try: data = request.get_json() or {} f_username = (data.get('f_username') or '').strip() f_password = data.get('f_password') or '' t_username = (data.get('t_username') or '').strip() t_password = data.get('t_password') or '' if not f_username or not f_password or not t_username or not t_password: return jsonify({"error": "All fields are required"}), 400 result = db.execute( "INSERT INTO user (f_username, f_password, t_username, t_password) VALUES (%s, %s, %s, %s)", [f_username, f_password, t_username, t_password] ) if result: return jsonify({"created": f_username}) return jsonify({"error": "Failed to create user"}), 500 except Exception as error: return self._handle_error(error, "Error creating user"), 500 def run(self, port=3000, debug=None): if debug is None: debug = _debug_enabled() try: verify_tables_once() except Exception as e: print(f"Cannot start server: {e}") exit(1) print(f'CM Bot DB API Listening at Port : {port}') self.app.run(host='0.0.0.0', port=port, debug=debug) def run_in_thread(self, port=3000, debug=False): """Run the Flask app in a separate thread""" try: verify_tables_once() except Exception as e: print(f"Cannot start server: {e}") return None def run_app(): print(f'CM Bot DB API Listening at Port : {port}') self.app.run(host='0.0.0.0', port=port, debug=debug, use_reloader=False) thread = threading.Thread(target=run_app, daemon=True) thread.start() return thread def create_app(): """WSGI factory used by gunicorn (`app.cm_api:create_app()`). Returns the Flask app object so gunicorn can serve it. Validates the schema once at boot (so a misconfigured DB fails fast) — request-time handlers don't repeat the check. """ app = CM_API().app verify_tables_once() return app if __name__ == '__main__': api = CM_API() api.run(port = 3000)