shivam9980 commited on
Commit
25a055e
Β·
verified Β·
1 Parent(s): 4b693e2

Upload 5 files

Browse files
Files changed (5) hide show
  1. Dockerfile +25 -0
  2. latex_generator.py +438 -0
  3. main.py +124 -0
  4. requirements.txt +3 -0
  5. run.sh +33 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM paperist/texlive-ja:latest
2
+
3
+ # Install Python 3 and pip
4
+ RUN apt-get update && apt-get install -y \
5
+ python3 \
6
+ python3-pip \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Create symlinks for python
10
+ RUN ln -sf /usr/bin/python3 /usr/bin/python
11
+
12
+ WORKDIR /app
13
+
14
+ # Copy requirements and install Python dependencies
15
+ COPY requirements.txt .
16
+ RUN python3 -m pip install --break-system-packages -r requirements.txt
17
+
18
+ # Copy application code
19
+ COPY . .
20
+
21
+ # Expose port
22
+ EXPOSE 8000
23
+
24
+ # Start the application
25
+ CMD ["python3", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
latex_generator.py ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple LaTeX Resume Generator
3
+ Generates LaTeX content that matches the working sample.tex format exactly
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import tempfile
9
+ import shutil
10
+ import re
11
+ import sys
12
+ from typing import Dict, Any, List, Union
13
+
14
+
15
+ class LatexResumeGenerator:
16
+ """Generates LaTeX resumes matching the sample.tex format"""
17
+
18
+ def __init__(self):
19
+ self.template = self._get_template()
20
+
21
+ def _escape_latex(self, text: Union[str, List]) -> str:
22
+ """Simple LaTeX escaping for normal user input"""
23
+ # Handle case where text might be a list (fix for the original error)
24
+ if isinstance(text, list):
25
+ text = ', '.join(str(item) for item in text)
26
+ elif not isinstance(text, str):
27
+ text = str(text) if text is not None else ""
28
+
29
+ if not text:
30
+ return ""
31
+
32
+ # Simple character escaping for normal text - order matters!
33
+ escaped_text = text
34
+
35
+ # Handle backslashes first to avoid double escaping
36
+ escaped_text = escaped_text.replace('\\', r'\textbackslash{}')
37
+
38
+ # Then handle other special characters
39
+ latex_special_chars = {
40
+ '{': r'\{',
41
+ '}': r'\}',
42
+ '&': r'\&',
43
+ '%': r'\%',
44
+ '$': r'\$',
45
+ '#': r'\#',
46
+ '^': r'\textasciicircum{}',
47
+ '_': r'\_',
48
+ '~': r'\textasciitilde{}'
49
+ }
50
+
51
+ for char, escape in latex_special_chars.items():
52
+ escaped_text = escaped_text.replace(char, escape)
53
+
54
+ return escaped_text
55
+
56
+ def _get_template(self) -> str:
57
+ """Returns minimal LaTeX template using only BasicTeX packages"""
58
+ return r"""
59
+ %-------------------------
60
+ % Resume in Latex
61
+ % Author : Shivam Sourav (adapted from Jake Gutierrez's template)
62
+ % License : MIT
63
+ %------------------------
64
+
65
+ \documentclass[letterpaper,11pt]{article}
66
+
67
+ \usepackage{latexsym}
68
+ \usepackage[margin=1in]{geometry}
69
+ \usepackage{titlesec}
70
+ \usepackage[usenames,dvipsnames]{color}
71
+ \usepackage{verbatim}
72
+ \usepackage{enumitem}
73
+ \usepackage[hidelinks]{hyperref}
74
+ \usepackage{fancyhdr}
75
+
76
+ \pagestyle{fancy}
77
+ \fancyhf{} % clear all header and footer fields
78
+ \fancyfoot{}
79
+ \renewcommand{\headrulewidth}{0pt}
80
+ \renewcommand{\footrulewidth}{0pt}
81
+
82
+ % Adjust margins
83
+ \addtolength{\oddsidemargin}{-0.5in}
84
+ \addtolength{\evensidemargin}{-0.5in}
85
+ \addtolength{\textwidth}{1.2in}
86
+ \addtolength{\topmargin}{-.5in}
87
+ \addtolength{\textheight}{1.5in}
88
+
89
+ \urlstyle{same}
90
+
91
+ \raggedbottom
92
+ \raggedright
93
+ \setlength{\tabcolsep}{0in}
94
+
95
+ % Sections formatting
96
+ \titleformat{\section}{
97
+ \vspace{-4pt}\scshape\raggedright\large
98
+ }{}{0em}{}[\color{black}\titlerule \vspace{-5pt}]
99
+
100
+ %-------------------------
101
+ % Custom commands
102
+ \newcommand{\resumeItem}[1]{
103
+ \item \small{
104
+ {#1 \vspace{-2pt}}
105
+ }
106
+ }
107
+
108
+ \newcommand{\resumeSubheading}[4]{
109
+ \vspace{-2pt}\item
110
+ \begin{tabular*}{0.97\textwidth}[t]{l@{\extracolsep{\fill}}r}
111
+ \textbf{#1} & #2 \\
112
+ \textit{\small#3} & \textit{\small#4} \\
113
+ \end{tabular*}\vspace{-5pt}
114
+ }
115
+
116
+ \newcommand{\resumeProjectHeading}[2]{
117
+ \vspace{-2pt}\item
118
+ \begin{tabular*}{0.97\textwidth}[t]{l@{\extracolsep{\fill}}r}
119
+ \small#1 & #2 \\
120
+ \end{tabular*}\vspace{-5pt}
121
+ }
122
+
123
+ \newcommand{\resumeSubHeadingListStart}{\begin{itemize}[leftmargin=0.15in, label={}]}
124
+ \newcommand{\resumeSubHeadingListEnd}{\end{itemize}}
125
+ \newcommand{\resumeItemListStart}{\begin{itemize}}
126
+ \newcommand{\resumeItemListEnd}{\end{itemize}\vspace{-5pt}}
127
+
128
+ %-------------------------------------------
129
+ %%%%%% RESUME STARTS HERE %%%%%%%%%%%%%%%%%%%%%%%%%%%%
130
+
131
+ \begin{document}
132
+
133
+ %----------HEADING----------
134
+ \begin{center}
135
+ \textbf{\Huge \scshape {{NAME}}} \\ \vspace{1pt}
136
+ {{CONTACT}}
137
+ \end{center}
138
+
139
+ %-----------EXPERIENCE-----------
140
+ \section{Professional Experience}
141
+ \resumeSubHeadingListStart
142
+ {{EXPERIENCE}}
143
+ \resumeSubHeadingListEnd
144
+
145
+ %-----------EDUCATION-----------
146
+ \section{Education}
147
+ \resumeSubHeadingListStart
148
+ {{EDUCATION}}
149
+ \resumeSubHeadingListEnd
150
+
151
+ %-----------PROJECTS-----------
152
+ \section{University Projects}
153
+ \resumeSubHeadingListStart
154
+ {{PROJECTS}}
155
+ \resumeSubHeadingListEnd
156
+
157
+ \section{Additional}
158
+ \begin{itemize}
159
+ {{SKILLS}}
160
+ \end{itemize}
161
+
162
+ \end{document}
163
+ """
164
+
165
+ def generate_latex(self, data: Dict[str, Any]) -> str:
166
+ """Generate LaTeX content from data"""
167
+ # Add debugging to see what data we receive
168
+ print("=== DEBUG: Received data ===", file=sys.stderr)
169
+ print(f"Raw data keys: {list(data.keys())}", file=sys.stderr)
170
+ print(f"Full data: {data}", file=sys.stderr)
171
+ print("=== END DEBUG ===", file=sys.stderr)
172
+
173
+ content = self.template
174
+
175
+ # Replace placeholders with escaped content
176
+ content = content.replace("{{NAME}}", self._escape_latex(data.get("name", "")))
177
+ content = content.replace("{{CONTACT}}", self._build_contact(data))
178
+ content = content.replace("{{EXPERIENCE}}", self._build_experience(data.get("experiences", [])))
179
+ content = content.replace("{{EDUCATION}}", self._build_education(data.get("education", [])))
180
+ content = content.replace("{{PROJECTS}}", self._build_projects(data.get("projects", [])))
181
+ content = content.replace("{{SKILLS}}", self._build_skills(data.get("skills", {})))
182
+
183
+ return content
184
+
185
+ def _build_contact(self, data: Dict[str, Any]) -> str:
186
+ """Build contact section exactly like sample.tex"""
187
+ email = self._escape_latex(data.get("email", ""))
188
+ location = self._escape_latex(data.get("location", ""))
189
+ linkedin_url = data.get("linkedin_url", "")
190
+ github_url = data.get("github_url", "")
191
+
192
+ print(f"DEBUG Contact - Email: '{email}', Location: '{location}', LinkedIn: '{linkedin_url}', GitHub: '{github_url}'", file=sys.stderr)
193
+
194
+ contact_parts = []
195
+
196
+ # Email and location on first line
197
+ if email:
198
+ contact_parts.append(f"\\href{{mailto:{email}}}{{\\underline{{{email}}}}}")
199
+ if location:
200
+ contact_parts.append(location)
201
+
202
+ first_line = " $|$ ".join(contact_parts)
203
+
204
+ # LinkedIn and GitHub on second line
205
+ second_line_parts = []
206
+ if linkedin_url:
207
+ clean_url = linkedin_url.replace("https://", "").replace("http://", "").replace("www.", "")
208
+ second_line_parts.append(f"\\href{{{linkedin_url}}}{{\\underline{{{clean_url}}}}}")
209
+
210
+ if github_url:
211
+ clean_url = github_url.replace("https://", "").replace("http://", "")
212
+ second_line_parts.append(f"\\href{{{github_url}}}{{\\underline{{{clean_url}}}}}")
213
+
214
+ if second_line_parts:
215
+ second_line = " $|$\n ".join(second_line_parts)
216
+ result = f"{first_line} \\\\ {second_line}"
217
+ else:
218
+ result = first_line
219
+
220
+ print(f"DEBUG Contact result: '{result}'", file=sys.stderr)
221
+ return result
222
+
223
+ def _build_experience(self, experiences: List[Dict[str, Any]]) -> str:
224
+ """Build experience section with simple escaping only"""
225
+ print(f"DEBUG Experience - Processing {len(experiences)} experiences", file=sys.stderr)
226
+
227
+ if not experiences:
228
+ print("DEBUG Experience - No experiences found", file=sys.stderr)
229
+ return ""
230
+
231
+ sections = []
232
+ for i, exp in enumerate(experiences):
233
+ print(f"DEBUG Experience {i}: {exp}", file=sys.stderr)
234
+
235
+ title = self._escape_latex(exp.get("title", ""))
236
+ dates = self._escape_latex(exp.get("dates", ""))
237
+ company = self._escape_latex(exp.get("company", ""))
238
+ location = self._escape_latex(exp.get("location", ""))
239
+
240
+ section = f""" \\resumeSubheading
241
+ {{{title}}}{{{dates}}}
242
+ {{{company}}}{{{location}}}
243
+ \\resumeItemListStart"""
244
+
245
+ # Add responsibilities with simple escaping only
246
+ responsibilities = exp.get("responsibilities", [])
247
+ print(f"DEBUG Responsibilities for exp {i}: {responsibilities}", file=sys.stderr)
248
+
249
+ for j, resp in enumerate(responsibilities):
250
+ # Handle both string and list inputs
251
+ if isinstance(resp, list):
252
+ resp = ', '.join(str(item) for item in resp)
253
+ elif not isinstance(resp, str):
254
+ resp = str(resp) if resp is not None else ""
255
+
256
+ if resp and resp.strip(): # Only add non-empty responsibilities
257
+ escaped_resp = self._escape_latex(resp.strip())
258
+ section += f"\n \\resumeItem{{{escaped_resp}}}"
259
+ print(f"DEBUG Added responsibility {j}: {escaped_resp[:50]}...", file=sys.stderr)
260
+
261
+ section += "\n \\resumeItemListEnd"
262
+ sections.append(section)
263
+
264
+ result = "\n".join(sections)
265
+ print(f"DEBUG Experience result length: {len(result)}", file=sys.stderr)
266
+ return result
267
+
268
+ def _build_projects(self, projects: List[Dict[str, Any]]) -> str:
269
+ """Build projects section with simple escaping only"""
270
+ print(f"DEBUG Projects - Processing {len(projects)} projects", file=sys.stderr)
271
+
272
+ if not projects:
273
+ print("DEBUG Projects - No projects found", file=sys.stderr)
274
+ return ""
275
+
276
+ sections = []
277
+ for i, proj in enumerate(projects):
278
+ print(f"DEBUG Project {i}: {proj}", file=sys.stderr)
279
+
280
+ title = self._escape_latex(proj.get("title", ""))
281
+
282
+ section = f""" \\resumeProjectHeading
283
+ {{\\textbf{{{title}}}}}{{}}
284
+ \\resumeItemListStart"""
285
+
286
+ # Add descriptions with simple escaping only
287
+ descriptions = proj.get("descriptions", [])
288
+ print(f"DEBUG Descriptions for project {i}: {descriptions}", file=sys.stderr)
289
+
290
+ for j, desc in enumerate(descriptions):
291
+ # Handle both string and list inputs
292
+ if isinstance(desc, list):
293
+ desc = ', '.join(str(item) for item in desc)
294
+ elif not isinstance(desc, str):
295
+ desc = str(desc) if desc is not None else ""
296
+
297
+ if desc and desc.strip(): # Only add non-empty descriptions
298
+ escaped_desc = self._escape_latex(desc.strip())
299
+ section += f"\n \\resumeItem{{{escaped_desc}}}"
300
+ print(f"DEBUG Added description {j}: {escaped_desc[:50]}...", file=sys.stderr)
301
+
302
+ section += "\n \\resumeItemListEnd"
303
+ sections.append(section)
304
+
305
+ result = "\n".join(sections)
306
+ print(f"DEBUG Projects result length: {len(result)}", file=sys.stderr)
307
+ return result
308
+
309
+ def _build_education(self, education: List[Dict[str, Any]]) -> str:
310
+ """Build education section exactly like sample.tex"""
311
+ print(f"DEBUG Education - Processing {len(education)} education entries", file=sys.stderr)
312
+
313
+ if not education:
314
+ print("DEBUG Education - No education found", file=sys.stderr)
315
+ return ""
316
+
317
+ sections = []
318
+ for i, edu in enumerate(education):
319
+ print(f"DEBUG Education {i}: {edu}", file=sys.stderr)
320
+
321
+ institution = self._escape_latex(edu.get("institution", ""))
322
+ date = self._escape_latex(edu.get("graduation_date", ""))
323
+ degree = self._escape_latex(edu.get("degree", ""))
324
+ gpa = self._escape_latex(edu.get("gpa", ""))
325
+
326
+ sections.append(f""" \\resumeSubheading
327
+ {{{institution}}}{{{date}}}
328
+ {{{degree}}}{{{gpa}}}""")
329
+
330
+ result = "\n".join(sections)
331
+ print(f"DEBUG Education result length: {len(result)}", file=sys.stderr)
332
+ return result
333
+
334
+ def _build_skills(self, skills: Dict[str, Any]) -> str:
335
+ """Build skills section with simple escaping - handles both strings and lists"""
336
+ print(f"DEBUG Skills - Processing skills: {skills}", file=sys.stderr)
337
+
338
+ if not skills:
339
+ print("DEBUG Skills - No skills found", file=sys.stderr)
340
+ return ""
341
+
342
+ # Improved skill name mapping to handle various formats
343
+ skill_names = {
344
+ "languages": "Languages",
345
+ "language": "Languages",
346
+ "programming_languages": "Programming Languages",
347
+ "tools": "Tools",
348
+ "tool": "Tools",
349
+ "technologies": "Technologies",
350
+ "technology": "Technologies",
351
+ "frameworks": "Frameworks",
352
+ "framework": "Frameworks",
353
+ "libraries": "Libraries",
354
+ "library": "Libraries",
355
+ "frameworks_libraries": "Frameworks \\& Libraries",
356
+ "frameworks_&_libraries": "Frameworks \\& Libraries",
357
+ "data_visualization": "Data \\& Visualization",
358
+ "data_&_visualization": "Data \\& Visualization",
359
+ "databases": "Databases",
360
+ "database": "Databases",
361
+ "concepts": "Concepts",
362
+ "soft_skills": "Soft Skills",
363
+ "concepts_soft_skills": "Concepts \\& Soft Skills",
364
+ "operating_systems": "Operating Systems",
365
+ "os": "Operating Systems"
366
+ }
367
+
368
+ items = []
369
+ for key, value in skills.items():
370
+ print(f"DEBUG Skill category '{key}': '{value}'", file=sys.stderr)
371
+ if value and str(value).strip(): # Check for non-empty values
372
+ # Clean up the key and get proper name
373
+ clean_key = key.lower().replace(" ", "_").replace("-", "_")
374
+ name = skill_names.get(clean_key, key.replace("_", " ").replace("-", " ").title())
375
+
376
+ # Handle both strings and lists properly
377
+ escaped_value = self._escape_latex(value)
378
+ items.append(f" \\item \\textbf{{{name}:}} {escaped_value}")
379
+ print(f"DEBUG Added skill item: {name}", file=sys.stderr)
380
+
381
+ result = "\n".join(items)
382
+ print(f"DEBUG Skills result: '{result}'", file=sys.stderr)
383
+ return result
384
+
385
+ def compile_to_pdf(self, latex_content: str, output_path: str) -> Dict[str, Any]:
386
+ """Compile LaTeX to PDF"""
387
+ # Create temp directory
388
+ with tempfile.TemporaryDirectory() as temp_dir:
389
+ tex_file = os.path.join(temp_dir, "resume.tex")
390
+ pdf_file = os.path.join(temp_dir, "resume.pdf")
391
+
392
+ # Write LaTeX file
393
+ with open(tex_file, "w", encoding="utf-8") as f:
394
+ f.write(latex_content)
395
+
396
+ # Compile with pdflatex
397
+ try:
398
+ # Run twice for references
399
+ for _ in range(2):
400
+ result = subprocess.run(
401
+ ["pdflatex", "-interaction=nonstopmode", "-output-directory", temp_dir, tex_file],
402
+ capture_output=True,
403
+ text=True
404
+ )
405
+
406
+ # Check if PDF was created
407
+ if os.path.exists(pdf_file):
408
+ # Copy to final location
409
+ shutil.copy2(pdf_file, output_path)
410
+ return {
411
+ "success": True,
412
+ "message": "PDF generated successfully",
413
+ "pdf_path": output_path
414
+ }
415
+ else:
416
+ return {
417
+ "success": False,
418
+ "message": f"PDF compilation failed: {result.stdout}",
419
+ "pdf_path": None
420
+ }
421
+
422
+ except Exception as e:
423
+ return {
424
+ "success": False,
425
+ "message": f"Error: {str(e)}",
426
+ "pdf_path": None
427
+ }
428
+
429
+
430
+ def generate_resume(data: Dict[str, Any], output_filename: str = "resume.pdf") -> Dict[str, Any]:
431
+ """Main function to generate resume"""
432
+ generator = LatexResumeGenerator()
433
+
434
+ # Generate LaTeX
435
+ latex_content = generator.generate_latex(data)
436
+
437
+ # Compile to PDF
438
+ return generator.compile_to_pdf(latex_content, output_filename)
main.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, UploadFile, File
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import FileResponse
4
+ from pydantic import BaseModel
5
+ import os
6
+ import tempfile
7
+ import subprocess
8
+ import json
9
+ from typing import Dict, Any, List, Optional
10
+ from latex_generator import generate_resume
11
+ import uvicorn
12
+
13
+ app = FastAPI(title="Resume Generator API")
14
+
15
+ # Configure CORS for Vercel frontend
16
+ origins = [
17
+ "http://localhost:3000", # Local development
18
+ "http://localhost:5173", # Vite dev server
19
+ "https://*.vercel.app", # All Vercel deployments
20
+ "https://resumegen-alpha.vercel.app", # Replace with your actual Vercel URL
21
+ ]
22
+
23
+ # If Railway environment, allow all origins (more permissive for deployment)
24
+ if os.getenv("RAILWAY_ENVIRONMENT"):
25
+ origins.append("*")
26
+
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=origins,
30
+ allow_credentials=True,
31
+ allow_methods=["*"],
32
+ allow_headers=["*"],
33
+ )
34
+
35
+ class ContactInfo(BaseModel):
36
+ email: str
37
+ location: Optional[str] = ""
38
+ linkedin_url: Optional[str] = ""
39
+ github_url: Optional[str] = ""
40
+
41
+ class Experience(BaseModel):
42
+ title: str
43
+ company: str
44
+ location: str
45
+ dates: str
46
+ responsibilities: List[str]
47
+
48
+ class Education(BaseModel):
49
+ institution: str
50
+ degree: str
51
+ graduation_date: str
52
+ gpa: Optional[str] = ""
53
+
54
+ class Project(BaseModel):
55
+ title: str
56
+ descriptions: List[str]
57
+
58
+ class ResumeData(BaseModel):
59
+ name: str
60
+ email: str
61
+ location: Optional[str] = ""
62
+ linkedin_url: Optional[str] = ""
63
+ github_url: Optional[str] = ""
64
+ experiences: List[Experience] = []
65
+ education: List[Education] = []
66
+ projects: List[Project] = []
67
+ skills: Dict[str, Any] = {}
68
+
69
+ @app.get("/")
70
+ def read_root():
71
+ return {"message": "Resume Generator Backend API", "status": "running"}
72
+
73
+ @app.get("/health")
74
+ def health_check():
75
+ # Check if LaTeX is available
76
+ try:
77
+ result = subprocess.run(['pdflatex', '--version'],
78
+ capture_output=True, text=True, timeout=5)
79
+ latex_available = result.returncode == 0
80
+ except:
81
+ latex_available = False
82
+
83
+ return {
84
+ "status": "healthy",
85
+ "latex_available": latex_available,
86
+ "environment": os.environ.get("RAILWAY_ENVIRONMENT", "local"),
87
+ "alternative_available": True # We have fallback options
88
+ }
89
+
90
+ @app.post("/generate-resume")
91
+ async def generate_resume_endpoint(data: ResumeData):
92
+ try:
93
+ resume_data = data.dict()
94
+
95
+ # Try LaTeX first, fall back to alternative if needed
96
+ with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_file:
97
+ try:
98
+ result = generate_resume(resume_data, tmp_file.name)
99
+
100
+ if result["success"]:
101
+ return FileResponse(
102
+ path=tmp_file.name,
103
+ filename="resume.pdf",
104
+ media_type="application/pdf"
105
+ )
106
+ else:
107
+ # Fallback: return LaTeX source or error message
108
+ raise HTTPException(
109
+ status_code=503,
110
+ detail="PDF generation temporarily unavailable. LaTeX not installed on server."
111
+ )
112
+ except Exception as latex_error:
113
+ # Return helpful error message
114
+ raise HTTPException(
115
+ status_code=503,
116
+ detail=f"Resume generation service temporarily unavailable: {str(latex_error)}"
117
+ )
118
+
119
+ except Exception as e:
120
+ raise HTTPException(status_code=500, detail=f"Error generating resume: {str(e)}")
121
+
122
+ if __name__ == "__main__":
123
+ port = int(os.environ.get("PORT", 8000))
124
+ uvicorn.run(app, host="0.0.0.0", port=port)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pdflatex
run.sh ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ echo "Starting ResumeGen backend setup and execution..."
3
+
4
+ # Make sure we're in the backend directory
5
+ cd "$(dirname "$0")"
6
+
7
+ echo "Step 1: Running install script..."
8
+ # Check if we're running as root or if sudo is not available
9
+ if [[ $EUID -eq 0 ]] || ! command -v sudo &> /dev/null; then
10
+ echo "Detected root access or no sudo available - setting ROOT_ENVIRONMENT"
11
+ export ROOT_ENVIRONMENT=true
12
+ fi
13
+
14
+ # Execute the install script from the parent scripts directory
15
+ bash scripts/install-latex-packages.sh
16
+
17
+ if [ $? -eq 0 ]; then
18
+ echo "βœ“ Install script completed successfully"
19
+ else
20
+ echo "βœ— Install script failed"
21
+ exit 1
22
+ fi
23
+
24
+ echo "Step 2: Starting Python application..."
25
+ # Run the main Python application
26
+ python3 main.py
27
+
28
+ if [ $? -eq 0 ]; then
29
+ echo "βœ“ Python application completed successfully"
30
+ else
31
+ echo "βœ— Python application failed"
32
+ exit 1
33
+ fi