Initial commit: LinkedIn Analyzer with Gemini 2.5 Flash and PHP Backend
This commit is contained in:
134
server/generate_cv.php
Normal file
134
server/generate_cv.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
// ============================================================================
|
||||
// Dynamic ATS-Optimized CV Generator (Backend)
|
||||
// ============================================================================
|
||||
|
||||
// Allow CORS from browser extension
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ensure composer dependencies are installed
|
||||
$autoloadPath = __DIR__ . '/vendor/autoload.php';
|
||||
if (!file_exists($autoloadPath)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Vendor folder not found. Please run 'composer install' in the server directory."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once $autoloadPath;
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
|
||||
// 1. Get POST Data
|
||||
$rawData = file_get_contents('php://input');
|
||||
$data = json_decode($rawData, true);
|
||||
|
||||
$jobDescription = $data['jobDescription'] ?? '';
|
||||
$apiKey = $data['apiKey'] ?? '';
|
||||
|
||||
if (empty($jobDescription) || empty($apiKey)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(["error" => "Missing jobDescription or apiKey in POST payload."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Build Gemini API Request
|
||||
$model = "gemini-2.5-flash";
|
||||
$geminiUrl = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=" . $apiKey;
|
||||
|
||||
$prompt = "You are an expert ATS CV tailor. Read the following job description and generate tailored content for my CV to maximize my chances of getting an interview.
|
||||
Return ONLY a valid JSON object with EXACTLY three keys: 'headline', 'summary', and 'skills'.
|
||||
The 'headline' should be a 5-6 word professional title relevant to the job.
|
||||
The 'summary' should be a 3-sentence powerful paragraph highlighting skills relevant to the job.
|
||||
The 'skills' should be a comma-separated list of 10 highly relevant ATS keywords.
|
||||
Do NOT use markdown blocks like ```json, just return raw JSON text.
|
||||
|
||||
Job Description:
|
||||
" . substr($jobDescription, 0, 4000);
|
||||
|
||||
$payload = json_encode([
|
||||
"contents" => [
|
||||
["parts" => [["text" => $prompt]]]
|
||||
],
|
||||
"generationConfig" => [
|
||||
"temperature" => 0.2, // Low temperature for consistent JSON
|
||||
"responseMimeType" => "application/json" // Force JSON output
|
||||
]
|
||||
]);
|
||||
|
||||
// 3. Call Google Gemini via cURL
|
||||
$ch = curl_init($geminiUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "Gemini API Error", "statusCode" => $httpCode, "details" => json_decode($response)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. Parse Gemini Response
|
||||
$responseData = json_decode($response, true);
|
||||
$aiText = $responseData['candidates'][0]['content']['parts'][0]['text'] ?? '{}';
|
||||
|
||||
// Clean markdown if Gemini still wrapped it in ```json ... ```
|
||||
$aiText = str_replace(['```json', '```'], '', $aiText);
|
||||
$aiText = trim($aiText);
|
||||
|
||||
$parsedJson = json_decode($aiText, true);
|
||||
|
||||
$headline = $parsedJson['headline'] ?? "Solutions Architect & Technical Leader";
|
||||
$summary = $parsedJson['summary'] ?? "Experienced professional with a strong background in software engineering and system architecture.";
|
||||
$skills = $parsedJson['skills'] ?? "Architecture, APIs, Cloud, Backend Systems, System Design";
|
||||
|
||||
// 5. Load HTML Template and Inject Data
|
||||
// Note: Put cv_template.html in the same directory as this script on your server
|
||||
$templatePath = __DIR__ . '/cv_template.html';
|
||||
if (!file_exists($templatePath)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "cv_template.html not found on server."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$html = file_get_contents($templatePath);
|
||||
$html = str_replace('{{JOB_HEADLINE}}', htmlspecialchars($headline), $html);
|
||||
$html = str_replace('{{TAILORED_SUMMARY}}', htmlspecialchars($summary), $html);
|
||||
$html = str_replace('{{DYNAMIC_SKILLS}}', htmlspecialchars($skills), $html);
|
||||
|
||||
// 6. Generate PDF via Dompdf
|
||||
try {
|
||||
$options = new Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
$options->set('defaultFont', 'Helvetica');
|
||||
$options->set('isRemoteEnabled', true);
|
||||
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html);
|
||||
$dompdf->setPaper('A4', 'portrait');
|
||||
$dompdf->render();
|
||||
|
||||
// 7. Return PDF as Base64 encoded string to the frontend
|
||||
$pdfOutput = $dompdf->output();
|
||||
$base64Pdf = base64_encode($pdfOutput);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"pdf" => $base64Pdf,
|
||||
"filename" => "Hamza_Ayed_Tailored_CV.pdf"
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(["error" => "PDF Generation Failed", "details" => $e->getMessage()]);
|
||||
}
|
||||
Reference in New Issue
Block a user