{
  "name": "latex-generator",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "latex-generator",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000001",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// \u2500\u2500 Generate LaTeX (Jake's Resume format) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst raw    = items[0].json;\nconst body   = raw.body || raw;\nconst resume = body.tailored_resume;\n\nif (!resume) throw new Error('tailored_resume is required in request body');\n\n// \u2500\u2500 Escape special LaTeX characters \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction esc(str) {\n  if (str === null || str === undefined) return '';\n  return String(str)\n    .replace(/\\\\/g, '\\\\textbackslash{}')\n    .replace(/&/g,  '\\\\&')\n    .replace(/%/g,  '\\\\%')\n    .replace(/_/g,  '\\\\_')\n    .replace(/#/g,  '\\\\#')\n    .replace(/\\$/g, '\\\\$')\n    .replace(/\\{/g, '\\\\{')\n    .replace(/\\}/g, '\\\\}')\n    .replace(/~/g,  '\\\\textasciitilde{}')\n    .replace(/\\^/g, '\\\\textasciicircum{}');\n}\n\n// \u2500\u2500 Contact header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst c = resume.contact || {};\nconst name = esc(c.name || 'Your Name');\nconst email    = c.email    ? esc(c.email)    : '';\nconst phone    = c.phone    ? esc(c.phone)    : '';\nconst linkedin = c.linkedin ? esc(c.linkedin) : '';\nconst github   = c.github   ? esc(c.github)   : '';\n\nconst parts = [];\nif (phone)    parts.push(phone);\nif (email)    parts.push(`\\\\href{mailto:${email}}{${email}}`);\nif (linkedin) parts.push(`\\\\href{https://${linkedin}}{${linkedin}}`);\nif (github)   parts.push(`\\\\href{https://${github}}{${github}}`);\nconst contactLine = parts.join(' $|$ ');\n\n// \u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet summarySection = '';\nif (resume.summary) {\n  summarySection = `\n%-----------SUMMARY-----------\n\\\\section{Summary}\n \\\\begin{itemize}[leftmargin=0.15in, label={}]\n    \\\\small{\\\\item{${esc(resume.summary)}}}\n \\\\end{itemize}\n`;\n}\n\n// \u2500\u2500 Education \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet educationSection = '';\nconst education = resume.education || [];\nif (education.length > 0) {\n  educationSection = `\n%-----------EDUCATION-----------\n\\\\section{Education}\n  \\\\resumeSubHeadingListStart\n`;\n  for (const edu of education) {\n    const gpaStr = edu.gpa ? `, GPA: ${esc(String(edu.gpa))}` : '';\n    educationSection += `    \\\\resumeSubheading\n      {${esc(edu.institution || '')}}{${esc(edu.dates || '')}}\n      {${esc(edu.degree || '')}${gpaStr}}{}\n`;\n  }\n  educationSection += `  \\\\resumeSubHeadingListEnd\n`;\n}\n\n// \u2500\u2500 Experience \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet experienceSection = '';\nconst experience = resume.experience || [];\nif (experience.length > 0) {\n  experienceSection = `\n%-----------EXPERIENCE-----------\n\\\\section{Experience}\n  \\\\resumeSubHeadingListStart\n`;\n  for (const exp of experience) {\n    experienceSection += `    \\\\resumeSubheading\n      {${esc(exp.title || '')}}{${esc(exp.dates || '')}}\n      {${esc(exp.company || '')}}{}\n      \\\\resumeItemListStart\n`;\n    for (const bullet of (exp.bullets || [])) {\n      experienceSection += `        \\\\resumeItem{${esc(bullet)}}\n`;\n    }\n    experienceSection += `      \\\\resumeItemListEnd\n`;\n  }\n  experienceSection += `  \\\\resumeSubHeadingListEnd\n`;\n}\n\n// \u2500\u2500 Projects \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet projectsSection = '';\nconst projects = resume.projects || [];\nif (projects.length > 0) {\n  projectsSection = `\n%-----------PROJECTS-----------\n\\\\section{Projects}\n  \\\\resumeSubHeadingListStart\n`;\n  for (const proj of projects) {\n    const techStr = (proj.tech || []).map(t => esc(t)).join(', ');\n    const heading = techStr\n      ? `\\\\textbf{${esc(proj.name || '')}} $|$ \\\\emph{\\\\small{${techStr}}}`\n      : `\\\\textbf{${esc(proj.name || '')}}`;\n    projectsSection += `    \\\\resumeProjectHeading\n      {${heading}}{}\n      \\\\resumeItemListStart\n`;\n    for (const bullet of (proj.bullets || [])) {\n      projectsSection += `        \\\\resumeItem{${esc(bullet)}}\n`;\n    }\n    projectsSection += `      \\\\resumeItemListEnd\n`;\n  }\n  projectsSection += `  \\\\resumeSubHeadingListEnd\n`;\n}\n\n// \u2500\u2500 Technical Skills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet skillsSection = '';\nconst skills = resume.skills || [];\nif (skills.length > 0) {\n  const skillLines = skills.map(s => {\n    const cat      = esc(s.category || '');\n    const itemList = (s.items || []).map(i => esc(i)).join(', ');\n    return `     \\\\textbf{${cat}}{: ${itemList}}`;\n  });\n  skillsSection = `\n%-----------TECHNICAL SKILLS-----------\n\\\\section{Technical Skills}\n \\\\begin{itemize}[leftmargin=0.15in, label={}]\n    \\\\small{\\\\item{\n${skillLines.join(' \\\\\\\\\\\n')}\n    }}\n \\\\end{itemize}\n`;\n}\n\n// \u2500\u2500 Assemble document \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst tex = `\\\\documentclass[letterpaper,11pt]{article}\n\\\\usepackage{latexsym}\n\\\\usepackage[empty]{fullpage}\n\\\\usepackage{titlesec}\n\\\\usepackage[usenames,dvipsnames]{color}\n\\\\usepackage{verbatim}\n\\\\usepackage{enumitem}\n\\\\usepackage[hidelinks]{hyperref}\n\\\\usepackage{fancyhdr}\n\\\\usepackage[english]{babel}\n\\\\usepackage{tabularx}\n\\\\input{glyphtounicode}\n\\\\pagestyle{fancy}\\\\fancyhf{}\\\\fancyfoot{}\n\\\\renewcommand{\\\\headrulewidth}{0pt}\\\\renewcommand{\\\\footrulewidth}{0pt}\n\\\\addtolength{\\\\oddsidemargin}{-0.5in}\\\\addtolength{\\\\evensidemargin}{-0.5in}\n\\\\addtolength{\\\\textwidth}{1in}\\\\addtolength{\\\\topmargin}{-.5in}\\\\addtolength{\\\\textheight}{1.0in}\n\\\\urlstyle{same}\\\\raggedbottom\\\\raggedright\\\\setlength{\\\\tabcolsep}{0in}\n\\\\titleformat{\\\\section}{\\\\vspace{-4pt}\\\\scshape\\\\raggedright\\\\large}{}{0em}{}[\\\\color{black}\\\\titlerule \\\\vspace{-5pt}]\n\\\\pdfgentounicode=1\n\\\\newcommand{\\\\resumeItem}[1]{\\\\item\\\\small{{#1 \\\\vspace{-2pt}}}}\n\\\\newcommand{\\\\resumeSubheading}[4]{\\\\vspace{-2pt}\\\\item\\\\begin{tabular*}{0.97\\\\textwidth}[t]{l@{\\\\extracolsep{\\\\fill}}r}\\\\textbf{#1} & #2 \\\\\\\\\\\\textit{\\\\small#3} & \\\\textit{\\\\small #4} \\\\\\\\\\\\end{tabular*}\\\\vspace{-7pt}}\n\\\\newcommand{\\\\resumeProjectHeading}[2]{\\\\item\\\\begin{tabular*}{0.97\\\\textwidth}{l@{\\\\extracolsep{\\\\fill}}r}\\\\small#1 & #2 \\\\\\\\\\\\end{tabular*}\\\\vspace{-7pt}}\n\\\\renewcommand\\\\labelitemii{$\\\\vcenter{\\\\hbox{\\\\tiny$\\\\bullet$}}$}\n\\\\newcommand{\\\\resumeSubHeadingListStart}{\\\\begin{itemize}[leftmargin=0.15in, label={}]}\n\\\\newcommand{\\\\resumeSubHeadingListEnd}{\\\\end{itemize}}\n\\\\newcommand{\\\\resumeItemListStart}{\\\\begin{itemize}}\n\\\\newcommand{\\\\resumeItemListEnd}{\\\\end{itemize}\\\\vspace{-5pt}}\n\\\\begin{document}\n\n%----------HEADING----------\n\\\\begin{center}\n    {\\\\Huge \\\\scshape ${name}} \\\\\\\\ \\\\vspace{1pt}\n    \\\\small ${contactLine}\n\\\\end{center}\n${summarySection}${educationSection}${experienceSection}${projectsSection}${skillsSection}\n\\\\end{document}\n`;\n\nreturn [{ json: { tex_source: tex } }];\n"
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000002",
      "name": "Generate LaTeX",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://latex:5000/compile",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { tex_source: $json.tex_source } }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000004",
      "name": "Compile LaTeX to PDF",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        720,
        300
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "cond-pdf-exists",
              "leftValue": "={{ $json.pdf_base64 !== null && $json.pdf_base64 !== undefined && $json.pdf_base64 !== '' }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000005",
      "name": "PDF Compiled?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        960,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const tex_source = $('Generate LaTeX').item.json.tex_source;\nconst pdf_base64  = items[0].json.pdf_base64;\nreturn [{ json: { tex_source, pdf_base64, compile_error: null } }];\n"
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000006",
      "name": "PDF Success",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        180
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const tex_source    = $('Generate LaTeX').item.json.tex_source;\nconst compile_error = items[0].json.error || 'LaTeX compilation failed. Download .tex to fix in Overleaf.';\nreturn [{ json: { tex_source, pdf_base64: null, compile_error } }];\n"
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000007",
      "name": "PDF Error",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        420
      ]
    },
    {
      "parameters": {
        "respondWith": "allIncomingItems",
        "options": {}
      },
      "id": "a1b2c3d4-0001-4000-8000-000000000008",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1440,
        300
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Generate LaTeX",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate LaTeX": {
      "main": [
        [
          {
            "node": "Compile LaTeX to PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compile LaTeX to PDF": {
      "main": [
        [
          {
            "node": "PDF Compiled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF Compiled?": {
      "main": [
        [
          {
            "node": "PDF Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "PDF Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF Success": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PDF Error": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "id": 4,
  "active": true
}