diff --git a/app.py b/app.py
index c01f783..1a3f4fb 100644
--- a/app.py
+++ b/app.py
@@ -143,13 +143,7 @@
-
-
@@ -174,13 +168,12 @@
const status = document.getElementById('status');
const github = document.getElementById('github').value;
- const linkedin = document.getElementById('linkedin').value;
const linkedinFile = document.getElementById('linkedinFile').files[0];
const jobDescription = document.getElementById('jobDescription').value;
- if (!github && !linkedin && !linkedinFile) {
+ if (!github && !linkedinFile) {
status.className = 'status error';
- status.textContent = 'Please provide at least one profile (GitHub, LinkedIn URL, or Data Export)';
+ status.textContent = 'Please provide at least one profile (GitHub username or LinkedIn Data Export)';
return;
}
@@ -200,7 +193,6 @@
if (linkedinFile) {
const formData = new FormData();
formData.append('github_username', github);
- formData.append('linkedin_url', linkedin);
formData.append('job_description', jobDescription);
formData.append('linkedin_data', linkedinFile);
@@ -216,7 +208,6 @@
},
body: JSON.stringify({
github_username: github,
- linkedin_url: linkedin,
job_description: jobDescription
})
});
@@ -265,26 +256,24 @@ def generate_resume():
Main endpoint to generate a tailored resume
Expected payload:
- - JSON: {"github_username": "...", "linkedin_url": "...", "job_description": "..."}
- - OR Multipart: form fields github_username, linkedin_url, job_description AND optional file linkedin_data
+ - JSON: {"github_username": "...", "job_description": "..."}
+ - OR Multipart: form fields github_username, job_description AND optional file linkedin_data
"""
try:
linkedin_file = None
if request.content_type and "multipart/form-data" in request.content_type:
github_username = request.form.get("github_username", "").strip()
- linkedin_url = request.form.get("linkedin_url", "").strip()
job_description = request.form.get("job_description", "").strip()
linkedin_file = request.files.get("linkedin_data")
else:
data = request.json
github_username = data.get("github_username", "").strip()
- linkedin_url = data.get("linkedin_url", "").strip()
job_description = data.get("job_description", "").strip()
if not job_description:
return jsonify({"error": "Job description is required"}), 400
- if not github_username and not linkedin_url and not linkedin_file:
+ if not github_username and not linkedin_file:
return (
jsonify({"error": "At least one profile or data export is required"}),
400,
@@ -312,9 +301,6 @@ def generate_resume():
# Cleanup
if os.path.exists(file_path):
os.remove(file_path)
- elif linkedin_url:
- linkedin_data = linkedin_scraper.scrape_profile(linkedin_url)
- profile_data["linkedin"] = linkedin_data
# Step 2: Analyze job description
job_requirements = job_analyzer.analyze(job_description)
diff --git a/app_production.py b/app_production.py
index d2d4e39..ccd8e4c 100644
--- a/app_production.py
+++ b/app_production.py
@@ -193,13 +193,7 @@
-
-
@@ -224,13 +218,12 @@
const status = document.getElementById('status');
const github = document.getElementById('github').value;
- const linkedin = document.getElementById('linkedin').value;
const linkedinFile = document.getElementById('linkedinFile').files[0];
const jobDescription = document.getElementById('jobDescription').value;
- if (!github && !linkedin && !linkedinFile) {
+ if (!github && !linkedinFile) {
status.className = 'status error';
- status.textContent = 'Please provide at least one profile (GitHub, LinkedIn URL, or Data Export)';
+ status.textContent = 'Please provide at least one profile (GitHub username or LinkedIn Data Export)';
return;
}
@@ -250,7 +243,6 @@
if (linkedinFile) {
const formData = new FormData();
formData.append('github_username', github);
- formData.append('linkedin_url', linkedin);
formData.append('job_description', jobDescription);
formData.append('linkedin_data', linkedinFile);
@@ -266,7 +258,6 @@
},
body: JSON.stringify({
github_username: github,
- linkedin_url: linkedin,
job_description: jobDescription
})
});
@@ -383,8 +374,8 @@ def generate_resume():
Main endpoint to generate a tailored resume
Expected payload:
- - JSON: {"github_username": "...", "linkedin_url": "...", "job_description": "..."}
- - OR Multipart: form fields github_username, linkedin_url, job_description AND optional file linkedin_data
+ - JSON: {"github_username": "...", "job_description": "..."}
+ - OR Multipart: form fields github_username, job_description AND optional file linkedin_data
Returns:
PDF file: application/pdf
@@ -395,7 +386,6 @@ def generate_resume():
if request.content_type and "multipart/form-data" in request.content_type:
github_username = request.form.get("github_username", "").strip()
- linkedin_url = request.form.get("linkedin_url", "").strip()
job_description = request.form.get("job_description", "").strip()
linkedin_file = request.files.get("linkedin_data")
else:
@@ -404,7 +394,6 @@ def generate_resume():
raise InvalidJobDescription("Request body is empty")
github_username = data.get("github_username", "").strip()
- linkedin_url = data.get("linkedin_url", "").strip()
job_description = data.get("job_description", "").strip()
logger.info(f"[{request.request_id}] Resume generation request")
@@ -413,18 +402,16 @@ def generate_resume():
(
github_username,
job_description,
- linkedin_url,
) = InputValidator.validate_request(
- github_username, job_description, linkedin_url
+ github_username, job_description
)
if (
not github_username
- and not linkedin_url
and not (linkedin_file and linkedin_file.filename)
):
raise ValidationError(
- "Please provide at least one profile source (GitHub Username, LinkedIn URL, or LinkedIn Data Export zip file)"
+ "Please provide at least one profile source (GitHub Username or LinkedIn Data Export zip file)"
)
logger.info(f"[{request.request_id}] Input validation passed")
@@ -471,17 +458,6 @@ def generate_resume():
logger.warning(
f"[{request.request_id}] LinkedIn export parsing failed: {e}"
)
- elif linkedin_url:
- try:
- logger.debug(f"[{request.request_id}] Scraping LinkedIn profile")
- linkedin_data = linkedin_scraper.scrape_profile(linkedin_url)
- profile_data["linkedin"] = linkedin_data
- logger.debug(
- f"[{request.request_id}] LinkedIn profile scraped successfully"
- )
- except Exception as e:
- logger.warning(f"[{request.request_id}] LinkedIn scraping failed: {e}")
- # Don't fail if LinkedIn scraping fails, continue with GitHub data
# Step 2: Analyze job description
try:
diff --git a/database/mongodb.py b/database/mongodb.py
index 9973e0b..848a122 100644
--- a/database/mongodb.py
+++ b/database/mongodb.py
@@ -123,7 +123,6 @@ class Resume(Document):
user_id = StringField(required=True, unique=False, index=True)
github_username = StringField()
- linkedin_url = StringField()
job_title = StringField()
job_description = StringField()
tailored_content = DictField(required=True)
@@ -198,7 +197,6 @@ class User(Document):
username = StringField(unique=True, required=True, index=True)
password_hash = StringField(required=True)
github_username = StringField()
- linkedin_url = StringField()
first_name = StringField()
last_name = StringField()
bio = StringField()
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index e98ac42..c450f06 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -74,7 +74,7 @@ This document provides technical reference for the production implementation of
1. Client submits form with:
- github_username (validated)
- job_description (validated)
- - linkedin_url (optional, validated)
+ - linkedin_data ZIP file (optional)
↓
2. Flask endpoint receives POST /api/v1/generate-resume
- Generate request_id: "a1b2c3d4"
@@ -230,11 +230,6 @@ User Input
│ 2. validate_job_description() │
│ - Check length (50-50000) │
│ - Raise InvalidJobDescription │
-├──────────────────────────────────┤
-│ 3. validate_linkedin_url() │
-│ - Check URL pattern │
-│ - Optional (empty allowed) │
-│ - Raise ValueError │
└──────────────────────────────────┘
↓
Cleaned Input (or Exception raised)
@@ -248,9 +243,7 @@ TestInputValidator
├── test_valid_github_username
├── test_invalid_github_username_format
├── test_valid_job_description
-├── test_invalid_job_description_too_short
-├── test_valid_linkedin_url
-└── test_invalid_linkedin_url
+└── test_invalid_job_description_too_short
TestGitHubScraper
├── test_scrape_profile_success (mocked)
diff --git a/docs/MONGODB_GUIDE.md b/docs/MONGODB_GUIDE.md
index 3943ad2..e017b0e 100644
--- a/docs/MONGODB_GUIDE.md
+++ b/docs/MONGODB_GUIDE.md
@@ -76,7 +76,6 @@ user = User.objects.create(
username="johndoe",
password_hash="hashed...",
github_username="johndoe-github",
- linkedin_url="https://linkedin.com/in/johndoe",
first_name="John",
last_name="Doe"
)
diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md
index 8f5d3d1..894b3fa 100644
--- a/docs/QUICK_START.md
+++ b/docs/QUICK_START.md
@@ -107,7 +107,6 @@ curl -X POST http://localhost:5000/api/v1/generate-resume \
-H "Content-Type: application/json" \
-d '{
"github_username": "torvalds",
- "linkedin_url": "",
"job_description": "We are looking for a C programmer with 10+ years of experience working on operating systems and kernel development. Required skills: C, Assembly, Linux, Git. Nice to have: Rust, Python."
}' \
-o resume.pdf
diff --git a/docs/README.md b/docs/README.md
index 70b120c..2f80d41 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -92,7 +92,7 @@ The application will start at `http://localhost:5000`
2. **Enter your profile information:**
- GitHub username (e.g., `torvalds`)
- - LinkedIn profile URL (optional)
+ - LinkedIn Data Export ZIP (optional)
3. **Paste the job description** for the position you're applying to
@@ -131,11 +131,13 @@ Generates a tailored resume
```json
{
"github_username": "username",
- "linkedin_url": "https://linkedin.com/in/username",
"job_description": "Full job description text..."
}
```
+**OR (with LinkedIn export)**
+Multipart form with `github_username`, `job_description`, and `linkedin_data` (file)
+
**Response:**
Returns PDF file as attachment
@@ -146,7 +148,7 @@ Health check endpoint
1. **Profile Scraping**:
- Extracts repositories, languages, and projects from GitHub
- - (LinkedIn support is placeholder - requires API access in production)
+ - Parses LinkedIn data export ZIP if provided
2. **Job Analysis**:
- Parses job description to extract required skills
diff --git a/generator/resume_generator.py b/generator/resume_generator.py
index 87b4b17..945d995 100644
--- a/generator/resume_generator.py
+++ b/generator/resume_generator.py
@@ -238,7 +238,6 @@ def _parse_gemini_response(self, response_text: str, profile_data: Dict) -> Dict
if github_data.get("username")
else ""
)
- parsed["linkedin_url"] = linkedin_data.get("url", "")
return parsed
except Exception as e:
@@ -337,7 +336,6 @@ def _generate_basic_resume(
if github_data.get("username")
else ""
),
- "linkedin_url": linkedin_data.get("url", ""),
"summary": linkedin_data.get("summary")
or github_data.get("bio", "Experienced software developer"),
"skills": final_skills,
diff --git a/scrapers/linkedin_scraper.py b/scrapers/linkedin_scraper.py
index d9a2fc4..780e3ac 100644
--- a/scrapers/linkedin_scraper.py
+++ b/scrapers/linkedin_scraper.py
@@ -171,52 +171,3 @@ def parse_export(self, zip_file_path: str) -> Dict:
except Exception as e:
print(f"Error parsing LinkedIn export: {e}")
return profile_data
- except Exception as e:
- print(f"Error parsing LinkedIn export: {e}")
- return profile_data
-
- def scrape_profile(self, linkedin_url: str) -> Dict:
- """
- Extract LinkedIn profile data
-
- In this implementation, we return a placeholder structure.
- In production, this would:
- - Use LinkedIn's API with OAuth
- - Or parse user-uploaded LinkedIn data export
-
- Args:
- linkedin_url: LinkedIn profile URL
-
- Returns:
- Dictionary containing profile data
- """
- # Extract username from URL
- username = self._extract_username(linkedin_url)
-
- # Log warning about placeholder usage
- print(
- "Warning: LinkedIn scraper is using placeholder data. "
- "LinkedIn scraping requires API access or manual data export. "
- "For best results, set up LinkedIn API access or ask users to provide their data."
- )
-
- # Return placeholder data
- # In production, this would fetch real data from LinkedIn API
- return {
- "url": linkedin_url,
- "username": username,
- "name": "",
- "headline": "",
- "summary": "",
- "experience": [],
- "education": [],
- "skills": [],
- "note": "LinkedIn scraping requires API access or manual data export",
- }
-
- def _extract_username(self, url: str) -> str:
- """Extract username from LinkedIn URL"""
- match = re.search(r"linkedin\.com/in/([^/]+)", url)
- if match:
- return match.group(1)
- return url
diff --git a/tests/test_comprehensive.py b/tests/test_comprehensive.py
index aec55de..ab52079 100644
--- a/tests/test_comprehensive.py
+++ b/tests/test_comprehensive.py
@@ -67,35 +67,6 @@ def test_job_description_cleanup(self):
result = InputValidator.validate_job_description(desc)
assert result == desc.strip()
- def test_valid_linkedin_url(self):
- """Test valid LinkedIn URL"""
- valid_urls = [
- "https://www.linkedin.com/in/username/",
- "https://linkedin.com/in/username",
- "http://www.linkedin.com/in/john-doe/",
- ]
-
- for url in valid_urls:
- result = InputValidator.validate_linkedin_url(url)
- assert result == url
-
- def test_empty_linkedin_url(self):
- """Test that empty LinkedIn URL is optional"""
- result = InputValidator.validate_linkedin_url("")
- assert result == ""
-
- def test_invalid_linkedin_url(self):
- """Test invalid LinkedIn URL"""
- invalid_urls = [
- "https://github.com/user",
- "not-a-url",
- "https://facebook.com/user",
- ]
-
- for url in invalid_urls:
- with pytest.raises(ValueError):
- InputValidator.validate_linkedin_url(url)
-
def test_validate_complete_request(self):
"""Test complete request validation"""
github = "torvalds"
@@ -103,15 +74,13 @@ def test_validate_complete_request(self):
"We are looking for a seasoned Python developer with 10+ years of experience "
"in building scalable systems. You will work with Django, PostgreSQL, and Docker."
)
- linkedin = "https://www.linkedin.com/in/test/"
- result_github, result_job, result_linkedin = InputValidator.validate_request(
- github, job_desc, linkedin
+ result_github, result_job = InputValidator.validate_request(
+ github, job_desc
)
assert result_github == github
assert result_job == job_desc
- assert result_linkedin == linkedin
class TestGitHubScraper:
@@ -315,7 +284,6 @@ def test_generate_resume_endpoint(
"/api/v1/generate-resume",
json={
"github_username": "torvalds",
- "linkedin_url": "",
"job_description": "We need a C programmer with 10+ years "
"of experience working on operating systems",
},
@@ -330,7 +298,6 @@ def test_generate_resume_missing_job_description(self, client):
"/api/v1/generate-resume",
json={
"github_username": "torvalds",
- "linkedin_url": "",
"job_description": "",
},
)
@@ -345,7 +312,6 @@ def test_generate_resume_missing_profiles(self, client):
"/api/v1/generate-resume",
json={
"github_username": "",
- "linkedin_url": "",
"job_description": "We need a developer with 5+ years experience",
},
)
diff --git a/utils/validators.py b/utils/validators.py
index d0f37e5..c7e01f6 100644
--- a/utils/validators.py
+++ b/utils/validators.py
@@ -22,9 +22,6 @@ class InputValidator:
MIN_JOB_DESC_LENGTH = 50
MAX_JOB_DESC_LENGTH = 50000
- # LinkedIn URL pattern
- LINKEDIN_URL_PATTERN = r"^https?://(?:www\.)?linkedin\.com/.*"
-
@classmethod
def validate_github_username(cls, username: str) -> str:
"""
@@ -83,55 +80,28 @@ def validate_job_description(cls, job_desc: str) -> str:
return job_desc
- @classmethod
- def validate_linkedin_url(cls, url: str) -> str:
- """
- Validate LinkedIn profile URL
-
- Args:
- url: LinkedIn URL to validate
-
- Returns:
- Cleaned URL
-
- Raises:
- ValueError: If URL is invalid
- """
- if not url:
- return "" # Optional field
-
- url = url.strip()
-
- if not re.match(cls.LINKEDIN_URL_PATTERN, url, re.IGNORECASE):
- raise ValueError("Invalid LinkedIn URL format")
-
- return url
-
@classmethod
def validate_request(
- cls, github_username: str, job_description: str, linkedin_url: str = ""
- ) -> Tuple[str, str, str]:
+ cls, github_username: str, job_description: str
+ ) -> Tuple[str, str]:
"""
Validate complete request input
Args:
github_username: GitHub username
job_description: Job description
- linkedin_url: LinkedIn profile URL (optional)
Returns:
- Tuple of (github_username, job_description, linkedin_url)
+ Tuple of (github_username, job_description)
Raises:
InvalidGitHubUsername: If username is invalid
InvalidJobDescription: If job description is invalid
- ValueError: If LinkedIn URL is invalid
"""
github_username = cls.validate_github_username(github_username)
job_description = cls.validate_job_description(job_description)
- linkedin_url = cls.validate_linkedin_url(linkedin_url)
- return github_username, job_description, linkedin_url
+ return github_username, job_description
@classmethod
def is_valid_github_username(cls, username: str) -> bool: