from flask import Flask, request, jsonify, send_from_directory, Blueprint from .cvs_client import CVSClient import os import sys import argparse # Get the directory where this app.py is located app_dir = os.path.dirname(os.path.abspath(__file__)) # Get the parent directory (cvs-proxy root) project_root = os.path.dirname(app_dir) # UI directory path ui_dir = os.path.join(project_root, 'ui') app = Flask(__name__, static_folder=ui_dir, static_url_path='') # Global variables to store configuration cvs_client = None app_config = {} # Create API Blueprint (will be registered with basepath as url_prefix) api_bp = Blueprint('api', __name__) # Initialize CVS Client using provided parameters def create_cvs_client(cvs_url, repos_checkout=None, cvs_module=None): """ Create CVS client from provided parameters :param cvs_url: CVS repository URL :type cvs_url: str :param repos_checkout: Path to the directory where repositories will be checked out :type repos_checkout: str, optional :param cvs_module: CVS module to work with :type cvs_module: str, optional :return: Configured CVS Client :rtype: CVSClient :raises ValueError: If CVS URL is not provided """ if not cvs_url: raise ValueError("CVS_URL must be provided as a command-line argument") return CVSClient(cvs_url, repos_checkout=repos_checkout, cvs_module=cvs_module) @api_bp.route('/ui/config.js', methods=['GET']) def serve_config(): """ Serve dynamic configuration as JavaScript This allows the frontend to access the basepath configuration """ basepath = app_config.get('basepath', '') config_js = f"window.APP_CONFIG = {{ basepath: '{basepath}' }};" return config_js, 200, {'Content-Type': 'application/javascript'} @api_bp.route('/api/v1/tree', methods=['GET']) def get_repository_tree(): """ Get repository tree structure Optional query param: module """ if not cvs_client: return jsonify({"error": "CVS Client not initialized"}), 500 module = request.args.get('module') try: tree = cvs_client.list_repository_tree(module) return jsonify(tree) except Exception as e: return jsonify({"error": str(e)}), 500 @api_bp.route('/api/v1/diff', methods=['GET']) def get_file_diff(): """ Get diff between two revisions of a file Required query params: file, rev1, rev2 """ if not cvs_client: return jsonify({"error": "CVS Client not initialized"}), 500 file_path = request.args.get('file') rev1 = request.args.get('rev1') rev2 = request.args.get('rev2') if not all([file_path, rev1, rev2]): return jsonify({"error": "Missing required parameters"}), 400 try: diff = cvs_client.get_file_diff(file_path, rev1, rev2) return jsonify({"diff": diff}) except Exception as e: return jsonify({"error": str(e)}), 500 @api_bp.route('/api/v1/history', methods=['GET']) def get_file_history(): """ Get revision history for a file Required query param: file """ if not cvs_client: return jsonify({"error": "CVS Client not initialized"}), 500 file_path = request.args.get('file') if not file_path: return jsonify({"error": "Missing file parameter"}), 400 try: history = cvs_client.get_file_history(file_path) return jsonify(history) except Exception as e: return jsonify({"error": str(e)}), 500 @api_bp.route('/api/v1/file', methods=['GET']) def get_file_content(): """ Get raw file content at a specific revision Required param: file Optional param: revision """ if not cvs_client: return jsonify({"error": "CVS Client not initialized"}), 500 file_path = request.args.get('file') revision = request.args.get('revision') if not file_path: return jsonify({"error": "Missing file parameter"}), 400 try: content = cvs_client.get_file_content(file_path, revision) return content except Exception as e: return jsonify({"error": str(e)}), 500 @api_bp.route('/api/v1/health', methods=['GET']) def health_check(): """ Simple health check endpoint """ return jsonify({ "status": "healthy", "cvs_client": "initialized" if cvs_client else "not initialized" }) @api_bp.route('/ui/', methods=['GET']) def index(): """ Serve the UI index.html """ return send_from_directory(ui_dir, 'index.html') @api_bp.route('/ui/', methods=['GET']) def serve_static(filename): """ Serve static files (CSS, JS, etc.) """ return send_from_directory(ui_dir, filename) def main(): """ Main entry point for the CVS Proxy application """ global cvs_client, app_config parser = argparse.ArgumentParser( description='CVS Proxy - A web proxy for CVS repositories' ) # CVS configuration arguments parser.add_argument( '--cvs-url', required=True, help='CVS repository URL (e.g., :pserver:user@host:/path/to/repo)' ) parser.add_argument( '--repo-checkouts', default='/tmp/cvs_checkouts', help='Path to the directory where repositories will be checked out (default: /tmp/cvs_checkouts)' ) parser.add_argument( '--cvs-module', default=None, help='CVS module to work with (optional)' ) # Flask configuration arguments parser.add_argument( '--host', default='0.0.0.0', help='Flask host to bind to (default: 0.0.0.0)' ) parser.add_argument( '--port', type=int, default=5000, help='Flask port to bind to (default: 5000)' ) parser.add_argument( '--basepath', default='', help='Base path for API requests (e.g., /api/cvs) (default: empty string for root)' ) parser.add_argument( '--debug', action='store_true', help='Enable Flask debug mode' ) args = parser.parse_args() # Store configuration app_config = { 'cvs_url': args.cvs_url, 'repo_checkouts': args.repo_checkouts, 'cvs_module': args.cvs_module, 'host': args.host, 'port': args.port, 'basepath': args.basepath, 'debug': args.debug } # Register the API Blueprint with basepath as url_prefix basepath = args.basepath if args.basepath else '' app.register_blueprint(api_bp, url_prefix=basepath) # Attempt to create CVS client at startup try: cvs_client = create_cvs_client( args.cvs_url, repos_checkout=args.repo_checkouts, cvs_module=args.cvs_module ) except ValueError as e: print(f"Error initializing CVS Client: {e}", file=sys.stderr) cvs_client = None print(f"Starting CVS Proxy on {args.host}:{args.port} with basepath: '{basepath}'") app.run(host=args.host, port=args.port, debug=args.debug) if __name__ == '__main__': main()