From f03a77c006fcd54b430651e5e4b9a9dafe93eff7 Mon Sep 17 00:00:00 2001 From: A69SHUBHAM Date: Wed, 29 Apr 2026 13:16:40 +0530 Subject: [PATCH 1/2] feat: add GitHub URL validation and prevent invalid submissions Signed-off-by: A69SHUBHAM --- backend/app.py | 19 ++++++++++++++++++ frontend/components/mainScreen.tsx | 32 ++++++++++++++---------------- frontend/package-lock.json | 11 ++++++++++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/backend/app.py b/backend/app.py index 951a1f9..f13d0ed 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 @@ -78,6 +88,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) @@ -121,6 +134,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 @@ -171,6 +187,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: 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" From 9fb5931c80fd55b59b10e32f4ea7c11c5b787551 Mon Sep 17 00:00:00 2001 From: A69SHUBHAM Date: Wed, 29 Apr 2026 16:52:27 +0530 Subject: [PATCH 2/2] feat: refactor config to use .env and add .env.example Signed-off-by: A69SHUBHAM --- README.md | 10 ++++++- backend/.env.example | 7 +++++ backend/app.py | 46 +++++++++++++++++++++++++----- backend/aurora_api.py | 24 +++++++++++++--- backend/embedding_url.py | 2 +- backend/requirements.txt | 3 +- frontend/components/mainScreen.tsx | 8 ++++-- frontend/services/api.ts | 2 +- 8 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 backend/.env.example diff --git a/README.md b/README.md index ac57d22..94ae5ed 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,15 @@ chmod +x ./bash.sh ### 3. Manual approach -### 3.1. Backend Setup +### 3.1 Environment Configuration + +Before running the backend or frontend, configure the environment variables: + +1. In the `backend` folder, copy `.env.example` to `.env`. + - **Important**: Consider adding a `GITHUB_TOKEN` to your `backend/.env` file to prevent GitHub API rate limits. +2. In the `frontend` folder, copy `.env.example` to `.env`. + +### 3.2. Backend Setup ```bash cd backend diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..9f84840 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,7 @@ +FLASK_HOST=127.0.0.1 +FLASK_PORT=5000 +OSDG_API_URL=http://20.73.166.85/label_text +OSDG_TOKEN=your_osdg_token_here +AURORA_API_URL=https://aurora-sdg.labs.vu.nl/classifier/classify/elsevier-sdg-multi +GITHUB_API_URL=https://api.github.com +GITHUB_TOKEN=your_github_token_here diff --git a/backend/app.py b/backend/app.py index f13d0ed..3e99fe8 100644 --- a/backend/app.py +++ b/backend/app.py @@ -9,7 +9,9 @@ from embedding_url import main as classify_url from aurora_api import main as aurora_classify import re +from dotenv import load_dotenv +load_dotenv() app = Flask(__name__) CORS(app) @@ -51,12 +53,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 @@ -106,7 +115,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 @@ -150,16 +159,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: @@ -192,9 +211,10 @@ def osdg_external_api(): return jsonify({'error': 'A valid GitHub repository URL is required'}), 400 # Call the external OSDG API + osdg_url = os.environ.get("OSDG_API_URL", "http://20.73.166.85/label_text") try: osdg_response = requests.post( - "http://20.73.166.85/label_text", + osdg_url, json={ "text": projectDescription }, @@ -205,10 +225,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 @@ -255,4 +285,6 @@ def osdg_external_api(): # }), 200 if __name__ == '__main__': - app.run(debug=True) + host = os.environ.get("FLASK_HOST", "127.0.0.1") + port = int(os.environ.get("FLASK_PORT", 5000)) + app.run(host=host, port=port, debug=True) diff --git a/backend/aurora_api.py b/backend/aurora_api.py index e31b1b3..7f31eca 100644 --- a/backend/aurora_api.py +++ b/backend/aurora_api.py @@ -1,3 +1,4 @@ +import os import requests import json from sdg_constants import SDG_LABELS_DICT as SDG_LABELS @@ -16,7 +17,7 @@ def main(text: str, project_name: str = None, project_url: str = None): Dictionary with predictions in standardized format """ try: - url = "https://aurora-sdg.labs.vu.nl/classifier/classify/elsevier-sdg-multi" + url = os.environ.get("AURORA_API_URL", "https://aurora-sdg.labs.vu.nl/classifier/classify/elsevier-sdg-multi") payload = json.dumps({"text": text}) headers = {'Content-Type': 'application/json'} response = requests.request("POST", url, headers=headers, data=payload) @@ -90,13 +91,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 +120,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/backend/embedding_url.py b/backend/embedding_url.py index f0c40bb..0f8fe96 100644 --- a/backend/embedding_url.py +++ b/backend/embedding_url.py @@ -10,7 +10,7 @@ from sdg_constants import SDG_LABELS, SDG_NAMES, SDG_DESCS # --- GitHub fetch utilities --- -GITHUB_API = "https://api.github.com" +GITHUB_API = os.environ.get("GITHUB_API_URL", "https://api.github.com") def parse_repo(url: str) -> Tuple[str, str]: """ diff --git a/backend/requirements.txt b/backend/requirements.txt index 4c8586c..d6d28be 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -6,4 +6,5 @@ numpy accelerate flask flask-cors -Flask-UUID \ No newline at end of file +Flask-UUID +python-dotenv \ No newline at end of file diff --git a/frontend/components/mainScreen.tsx b/frontend/components/mainScreen.tsx index 2cd02d2..9f7565d 100644 --- a/frontend/components/mainScreen.tsx +++ b/frontend/components/mainScreen.tsx @@ -87,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/services/api.ts b/frontend/services/api.ts index 006d226..0b9e973 100644 --- a/frontend/services/api.ts +++ b/frontend/services/api.ts @@ -5,7 +5,7 @@ import { SDGClassificationResponse, } from "@/types/main"; -const API_BASE_URL = "http://127.0.0.1:5000/"; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://127.0.0.1:5000/"; const apiClient = axios.create({ baseURL: API_BASE_URL,