Files
cv/server/generate_cv.php
2026-05-17 01:43:14 +03:00

153 lines
5.5 KiB
PHP

<?php
// ============================================================================
// Dynamic ATS-Optimized CV Generator & AI Proxy (Backend)
// ============================================================================
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;
}
$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'."]);
exit;
}
require_once $autoloadPath;
use Dompdf\Dompdf;
use Dompdf\Options;
use Dotenv\Dotenv;
// Load .env if it exists
if (file_exists(__DIR__ . '/.env')) {
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
}
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
$action = $data['action'] ?? 'generateText';
// Prioritize API key from .env over frontend payload
$apiKey = $_ENV['GEMINI_API_KEY'] ?? getenv('GEMINI_API_KEY') ?: ($data['apiKey'] ?? '');
if (empty($apiKey)) {
http_response_code(400);
echo json_encode(["error" => "Missing apiKey. Please set GEMINI_API_KEY in .env or pass it in request."]);
exit;
}
$model = "gemini-2.5-flash";
$geminiUrl = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=" . $apiKey;
// ==========================================
// ACTION 1: Generate ATS PDF CV
// ==========================================
if ($action === 'generatePdf') {
$jobDescription = $data['jobDescription'] ?? '';
$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, "responseMimeType" => "application/json"]
]);
$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", "details" => json_decode($response)]);
exit;
}
$responseData = json_decode($response, true);
$aiText = $responseData['candidates'][0]['content']['parts'][0]['text'] ?? '{}';
$aiText = str_replace(['```json', '```'], '', $aiText);
$parsedJson = json_decode(trim($aiText), true);
$headline = $parsedJson['headline'] ?? "Solutions Architect & Technical Leader";
$summary = $parsedJson['summary'] ?? "Experienced professional with a strong background in software engineering.";
$skills = $parsedJson['skills'] ?? "Architecture, APIs, Cloud, Backend Systems, System Design";
$templatePath = __DIR__ . '/cv_template.html';
$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);
try {
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('defaultFont', 'Helvetica');
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$pdfOutput = $dompdf->output();
echo json_encode([
"success" => true,
"pdf" => base64_encode($pdfOutput),
"filename" => "Tailored_CV.pdf"
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(["error" => "PDF Generation Failed", "details" => $e->getMessage()]);
}
exit;
}
// ==========================================
// ACTION 2: Standard Proxy (Text generation)
// ==========================================
if ($action === 'generateText') {
$prompt = $data['prompt'] ?? '';
$payload = json_encode([
"contents" => [["parts" => [["text" => $prompt]]]],
"generationConfig" => ["temperature" => 0.7, "maxOutputTokens" => 2048]
]);
$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($httpCode);
echo $response;
exit;
}
// Pass the exact Gemini response back to the extension
echo $response;
exit;
}