diff --git a/backend/app.py b/backend/app.py index 951a1f9..7a77e66 100644 --- a/backend/app.py +++ b/backend/app.py @@ -8,11 +8,18 @@ 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 app = Flask(__name__) CORS(app) +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,6 +37,9 @@ 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 @@ -41,12 +51,19 @@ def classify_aurora(): project_name=projectName, project_url=projectUrl ) + + if "error" in aurora_result: + print(f"Aurora API returned an error: {aurora_result['error']}") + return jsonify({ + "error": aurora_result["error"], + "message": aurora_result.get("message", "Aurora API classification failed") + }), 502 print("Aurora API model completed successfully") except Exception as e: print(f"Aurora API model failed: {str(e)}") return jsonify({ - "error": str(e), + "error": "Internal processing error occurred.", "message": "Aurora API classification failed" }), 500 @@ -78,6 +95,9 @@ 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) @@ -93,7 +113,7 @@ def classify_st_description(): except Exception as e: print(f"ST Description model failed: {str(e)}") st_desc_result = { - "error": str(e), + "error": "Internal processing error occurred.", "message": "Sentence Transformer Description model classification failed" } return jsonify(st_desc_result), 500 @@ -121,6 +141,9 @@ 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 @@ -134,16 +157,26 @@ def classify_st_url(): except ValueError as ve: print(f"ST URL model invalid URL: {str(ve)}") return jsonify({'error': str(ve)}), 400 + except requests.exceptions.Timeout: + return jsonify({ + "error": "The request to GitHub timed out. Please try again later.", + "message": "Network timeout error" + }), 504 + except requests.exceptions.ConnectionError: + return jsonify({ + "error": "Failed to connect to GitHub. Please check your network connection.", + "message": "Network connection error" + }), 502 except requests.exceptions.HTTPError as he: print(f"ST URL model HTTP Error: {str(he)}") return jsonify({ - "error": f"Failed to fetch repository data. Please ensure the repository is public and exists. ({str(he)})", + "error": "Failed to fetch repository data. Please ensure the repository is public and exists.", "message": "Sentence Transformer URL model classification failed" }), 400 except Exception as e: print(f"ST URL model failed: {str(e)}") return jsonify({ - "error": str(e), + "error": "An internal error occurred while processing the repository URL.", "message": "Sentence Transformer URL model classification failed" }), 500 else: @@ -171,6 +204,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: @@ -186,10 +222,20 @@ def osdg_external_api(): ) osdg_response.raise_for_status() # Raise an error for bad status codes osdg_result = osdg_response.json() + except requests.exceptions.Timeout: + return jsonify({ + "error": "The request to the OSDG API timed out.", + "message": "Network timeout error" + }), 504 + except requests.exceptions.ConnectionError: + return jsonify({ + "error": "Failed to connect to the OSDG API.", + "message": "Network connection error" + }), 502 except requests.exceptions.RequestException as e: print(f"OSDG API request failed: {str(e)}") return jsonify({ - "error": f"Failed to connect to OSDG API: {str(e)}", + "error": "Failed to communicate with OSDG API.", "message": "OSDG API classification failed" }), 500 diff --git a/backend/aurora_api.py b/backend/aurora_api.py index e31b1b3..bac49a6 100644 --- a/backend/aurora_api.py +++ b/backend/aurora_api.py @@ -90,13 +90,28 @@ def main(text: str, project_name: str = None, project_url: str = None): return formatted_result + except requests.exceptions.Timeout: + return { + "project_name": project_name or "Unknown", + "project_url": project_url or "", + "sdg_predictions": {}, + "error": "The request to the Aurora API timed out.", + "message": "Network timeout error" + } + except requests.exceptions.ConnectionError: + return { + "project_name": project_name or "Unknown", + "project_url": project_url or "", + "sdg_predictions": {}, + "error": "Failed to connect to the Aurora API.", + "message": "Network connection error" + } except requests.exceptions.RequestException as e: - return { "project_name": project_name or "Unknown", "project_url": project_url or "", "sdg_predictions": {}, - "error": str(e), + "error": "Failed to communicate with Aurora API.", "message": "Aurora API request failed" } except Exception as e: @@ -104,6 +119,6 @@ def main(text: str, project_name: str = None, project_url: str = None): "project_name": project_name or "Unknown", "project_url": project_url or "", "sdg_predictions": {}, - "error": f"{type(e).__name__}: {str(e)}", + "error": "An internal error occurred during classification.", "message": "Aurora API processing failed" } diff --git a/frontend/components/mainScreen.tsx b/frontend/components/mainScreen.tsx index 6f51781..9f7565d 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); @@ -89,9 +87,13 @@ const MainScreen: React.FC<{ // console.log("API Response:", response.data); setResults(response as ResultsData); - } catch (error) { + } catch (error: any) { console.error("Error:", error); - setUploadMsg("Text Analyzing Failed. Please try again."); + const backendError = error.response?.data?.message || error.response?.data?.error; + const fallbackError = error.message === "Network Error" + ? "Network error: Unable to reach the server. Please try again later." + : "Text Analyzing Failed. Please try again."; + setUploadMsg(backendError || fallbackError); } finally { setIsUploading(false); } 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"