diff --git a/backend/app.py b/backend/app.py index 951a1f9..cbb198f 100644 --- a/backend/app.py +++ b/backend/app.py @@ -8,11 +8,38 @@ from embedding_description import main as classify_description from embedding_url import main as classify_url from aurora_api import main as aurora_classify +import re +import logging +from dotenv import load_dotenv +load_dotenv() app = Flask(__name__) CORS(app) +# Configure logging +log_level_str = os.environ.get("LOG_LEVEL", "INFO").upper() +log_level = getattr(logging, log_level_str, logging.INFO) +logging.basicConfig( + level=log_level, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) + +@app.before_request +def log_request_info(): + app.logger.info(f"Incoming Request: {request.method} {request.url} - Size: {request.content_length} bytes") + +@app.after_request +def log_response_info(response): + app.logger.info(f"Outgoing Response: {request.method} {request.url} - Status: {response.status} - Size: {response.content_length} bytes") + return response + +def is_valid_github_url(url): + if not url: + return False + pattern = r'^https?:\/\/(www\.)?github\.com\/[\w.-]+\/[\w.-]+\/?$' + return re.match(pattern, url) is not None + @app.route('/api/hello', methods=['GET']) @@ -30,11 +57,14 @@ def classify_aurora(): if not projectDescription: return jsonify({'error': 'Project description is required'}), 400 + + if not is_valid_github_url(projectUrl): + return jsonify({'error': 'A valid GitHub repository URL is required'}), 400 # 1. Aurora API Model (text-based) - print("\n===== RUNNING AURORA API MODEL =====") + app.logger.info("===== RUNNING AURORA API MODEL =====") try: aurora_result = aurora_classify( text=projectDescription, @@ -42,9 +72,9 @@ def classify_aurora(): project_url=projectUrl ) - print("Aurora API model completed successfully") + app.logger.info("Aurora API model completed successfully") except Exception as e: - print(f"Aurora API model failed: {str(e)}") + app.logger.error(f"Aurora API model failed: {str(e)}", exc_info=True) return jsonify({ "error": str(e), "message": "Aurora API classification failed" @@ -78,10 +108,13 @@ def classify_st_description(): if not projectDescription: return jsonify({'error': 'Project description is required'}), 400 + + if not is_valid_github_url(projectUrl): + return jsonify({'error': 'A valid GitHub repository URL is required'}), 400 # 3. Sentence Transformer Description Model (text-based) - print("\n===== RUNNING SENTENCE TRANSFORMER DESCRIPTION MODEL =====") + app.logger.info("===== RUNNING SENTENCE TRANSFORMER DESCRIPTION MODEL =====") try: st_desc_result = classify_description( project_description=projectDescription, @@ -89,9 +122,9 @@ def classify_st_description(): project_url=projectUrl ) - print("ST Description model completed successfully") + app.logger.info("ST Description model completed successfully") except Exception as e: - print(f"ST Description model failed: {str(e)}") + app.logger.error(f"ST Description model failed: {str(e)}", exc_info=True) st_desc_result = { "error": str(e), "message": "Sentence Transformer Description model classification failed" @@ -121,27 +154,30 @@ def classify_st_url(): if not projectDescription: return jsonify({'error': 'Project description is required'}), 400 + + if not is_valid_github_url(projectUrl): + return jsonify({'error': 'A valid GitHub repository URL is required'}), 400 # 2. Sentence Transformer URL Model (GitHub URL-based) - print("\n===== RUNNING SENTENCE TRANSFORMER URL MODEL =====") + app.logger.info("===== RUNNING SENTENCE TRANSFORMER URL MODEL =====") if projectUrl: try: st_url_result = classify_url(projectUrl) - print("ST URL model completed successfully") + app.logger.info("ST URL model completed successfully") except ValueError as ve: - print(f"ST URL model invalid URL: {str(ve)}") + app.logger.warning(f"ST URL model invalid URL: {str(ve)}") return jsonify({'error': str(ve)}), 400 except requests.exceptions.HTTPError as he: - print(f"ST URL model HTTP Error: {str(he)}") + app.logger.error(f"ST URL model HTTP Error: {str(he)}", exc_info=True) return jsonify({ "error": f"Failed to fetch repository data. Please ensure the repository is public and exists. ({str(he)})", "message": "Sentence Transformer URL model classification failed" }), 400 except Exception as e: - print(f"ST URL model failed: {str(e)}") + app.logger.error(f"ST URL model failed: {str(e)}", exc_info=True) return jsonify({ "error": str(e), "message": "Sentence Transformer URL model classification failed" @@ -171,6 +207,9 @@ def osdg_external_api(): if not projectDescription: return jsonify({'error': 'Project description is required'}), 400 + + if not is_valid_github_url(projectUrl): + return jsonify({'error': 'A valid GitHub repository URL is required'}), 400 # Call the external OSDG API try: @@ -187,7 +226,7 @@ def osdg_external_api(): osdg_response.raise_for_status() # Raise an error for bad status codes osdg_result = osdg_response.json() except requests.exceptions.RequestException as e: - print(f"OSDG API request failed: {str(e)}") + app.logger.error(f"OSDG API request failed: {str(e)}", exc_info=True) return jsonify({ "error": f"Failed to connect to OSDG API: {str(e)}", "message": "OSDG API classification failed" diff --git a/backend/aurora_api.py b/backend/aurora_api.py index e31b1b3..46efd97 100644 --- a/backend/aurora_api.py +++ b/backend/aurora_api.py @@ -1,7 +1,10 @@ import requests import json +import logging from sdg_constants import SDG_LABELS_DICT as SDG_LABELS +logger = logging.getLogger(__name__) + def main(text: str, project_name: str = None, project_url: str = None): """ @@ -74,9 +77,9 @@ def main(text: str, project_name: str = None, project_url: str = None): if score > 0.5: sdg_predictions[sdg_name] = float(f"{float(score):.3f}") else: - print(f"DEBUG - Warning: Could not extract SDG name from prediction {idx}") + logger.warning(f"Could not extract SDG name from prediction {idx}") else: - print(f"DEBUG - Warning: Prediction {idx} is not a dict: {type(pred)}") + logger.warning(f"Prediction {idx} is not a dict: {type(pred)}") # Format output to match embedding_url.py structure formatted_result = { diff --git a/frontend/components/mainScreen.tsx b/frontend/components/mainScreen.tsx index 6f51781..2cd02d2 100644 --- a/frontend/components/mainScreen.tsx +++ b/frontend/components/mainScreen.tsx @@ -31,33 +31,31 @@ const MainScreen: React.FC<{ const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if ( - !projectName || - !projectUrl || - !projectDescription - // !problemStatement || - // !longTermGoal || - // !solutionApproach || - // !targetAudience - ) { + const trimmedName = projectName.trim(); + const trimmedUrl = projectUrl.trim(); + const trimmedDesc = projectDescription.trim(); + + if (!trimmedName || !trimmedUrl || !trimmedDesc) { setUploadMsg("Please fill in all required fields before submitting."); return; } - if (projectUrl.includes("github.com") === false) { - setUploadMsg("Please enter a valid GitHub repository URL."); + // GitHub URL validation regex + const githubUrlRegex = /^https?:\/\/(www\.)?github\.com\/[\w.-]+\/[\w.-]+\/?$/; + if (!githubUrlRegex.test(trimmedUrl)) { + setUploadMsg("Please enter a valid GitHub repository URL (e.g., https://github.com/username/repository)."); return; } const finalizedData = { - projectName: projectName, - projectUrl: projectUrl, - projectDescription: projectDescription, + projectName: trimmedName, + projectUrl: trimmedUrl, + projectDescription: trimmedDesc, }; - localStorage.setItem("projectDescription", projectDescription); - localStorage.setItem("projectName", projectName); - localStorage.setItem("projectUrl", projectUrl); + localStorage.setItem("projectDescription", trimmedDesc); + localStorage.setItem("projectName", trimmedName); + localStorage.setItem("projectUrl", trimmedUrl); try { setIsUploading(true); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6dad3ad..5ad7987 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -277,6 +277,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -320,6 +321,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1837,6 +1839,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1906,6 +1909,7 @@ "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", @@ -2423,6 +2427,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3378,6 +3383,7 @@ "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3552,6 +3558,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5806,6 +5813,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5815,6 +5823,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -6555,6 +6564,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6714,6 +6724,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver"