init commit

This commit is contained in:
David Melendez
2026-01-14 22:38:44 +01:00
parent 4e0c415f0b
commit e25d53d054
124 changed files with 21653 additions and 1 deletions

View File

@@ -0,0 +1,380 @@
<?php
/**
* Resume Service
* Professional Resume Builder - Resume Management Service
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Services;
use App\Models\Resume;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Response;
use Barryvdh\DomPDF\Facade\Pdf;
/**
* Resume Service
* Handles resume creation, management, and operations
*/
class ResumeService
{
/**
* Available resume templates
*/
protected array $templates = [
'professional' => [
'id' => 'professional',
'name' => 'Professional Classic',
'description' => 'Clean and professional design perfect for corporate environments',
'preview' => 'templates/professional-preview.jpg',
'category' => 'Professional'
],
'modern' => [
'id' => 'modern',
'name' => 'Modern Creative',
'description' => 'Contemporary design with creative elements for modern companies',
'preview' => 'templates/modern-preview.jpg',
'category' => 'Creative'
],
'executive' => [
'id' => 'executive',
'name' => 'Executive',
'description' => 'Sophisticated layout for senior-level positions',
'preview' => 'templates/executive-preview.jpg',
'category' => 'Executive'
],
'minimal' => [
'id' => 'minimal',
'name' => 'Minimal Clean',
'description' => 'Simple and clean design focusing on content',
'preview' => 'templates/minimal-preview.jpg',
'category' => 'Minimal'
],
'technical' => [
'id' => 'technical',
'name' => 'Technical',
'description' => 'Optimized for technical and engineering roles',
'preview' => 'templates/technical-preview.jpg',
'category' => 'Technical'
]
];
/**
* Get all resumes for a specific user
*/
public function getUserResumes(int $userId): Collection
{
return Resume::where('user_id', $userId)
->orderBy('updated_at', 'desc')
->get();
}
/**
* Create a new resume
*/
public function createResume(array $resumeData): Resume
{
// Set default values
$resumeData['is_active'] = true;
$resumeData['is_completed'] = false;
$resumeData['is_public'] = false;
// Initialize empty sections
$resumeData['personal_info'] = $resumeData['personal_info'] ?? [];
$resumeData['work_experiences'] = $resumeData['work_experiences'] ?? [];
$resumeData['education'] = $resumeData['education'] ?? [];
$resumeData['skills'] = $resumeData['skills'] ?? [];
$resumeData['languages'] = $resumeData['languages'] ?? [];
$resumeData['certifications'] = $resumeData['certifications'] ?? [];
$resumeData['projects'] = $resumeData['projects'] ?? [];
$resumeData['references'] = $resumeData['references'] ?? [];
$resumeData['custom_sections'] = $resumeData['custom_sections'] ?? [];
$resumeData['settings'] = $resumeData['settings'] ?? $this->getDefaultSettings();
$resume = Resume::create($resumeData);
logger()->info('Resume created', [
'resume_id' => $resume->id,
'user_id' => $resume->user_id,
'title' => $resume->title,
]);
return $resume;
}
/**
* Update an existing resume
*/
public function updateResume(Resume $resume, array $resumeData): Resume
{
// Update completion status based on content
if ($this->shouldMarkAsCompleted($resumeData)) {
$resumeData['is_completed'] = true;
}
$resume->update($resumeData);
logger()->info('Resume updated', [
'resume_id' => $resume->id,
'user_id' => $resume->user_id,
'completion' => $resume->completion_percentage,
]);
return $resume;
}
/**
* Delete a resume
*/
public function deleteResume(Resume $resume): bool
{
$resumeId = $resume->id;
$userId = $resume->user_id;
$deleted = $resume->delete();
if ($deleted) {
logger()->info('Resume deleted', [
'resume_id' => $resumeId,
'user_id' => $userId,
]);
}
return $deleted;
}
/**
* Duplicate a resume
*/
public function duplicateResume(Resume $resume, string $newTitle): Resume
{
$resumeData = $resume->toArray();
// Remove unique identifiers
unset($resumeData['id'], $resumeData['slug'], $resumeData['public_url']);
// Set new title and reset status
$resumeData['title'] = $newTitle;
$resumeData['is_public'] = false;
$resumeData['created_at'] = now();
$resumeData['updated_at'] = now();
$newResume = Resume::create($resumeData);
logger()->info('Resume duplicated', [
'original_resume_id' => $resume->id,
'new_resume_id' => $newResume->id,
'user_id' => $resume->user_id,
]);
return $newResume;
}
/**
* Generate PDF for a resume
*/
public function generatePdf(Resume $resume): Response
{
$template = $this->getTemplate($resume->template_id ?? 'professional');
$pdf = Pdf::loadView('pdf.resume.' . $template['id'], [
'resume' => $resume,
'template' => $template
]);
$fileName = str()->slug($resume->title) . '-resume.pdf';
logger()->info('PDF generated', [
'resume_id' => $resume->id,
'user_id' => $resume->user_id,
'template' => $template['id'],
]);
return $pdf->download($fileName);
}
/**
* Make resume public
*/
public function makePublic(Resume $resume): Resume
{
if (!$resume->public_url) {
$resume->public_url = $resume->generatePublicUrl();
}
$resume->update(['is_public' => true]);
logger()->info('Resume made public', [
'resume_id' => $resume->id,
'user_id' => $resume->user_id,
'public_url' => $resume->public_url,
]);
return $resume;
}
/**
* Make resume private
*/
public function makePrivate(Resume $resume): Resume
{
$resume->update(['is_public' => false]);
logger()->info('Resume made private', [
'resume_id' => $resume->id,
'user_id' => $resume->user_id,
]);
return $resume;
}
/**
* Get available templates
*/
public function getAvailableTemplates(): array
{
return $this->templates;
}
/**
* Get specific template
*/
public function getTemplate(string $templateId): array
{
return $this->templates[$templateId] ?? $this->templates['professional'];
}
/**
* Get resume analytics
*/
public function getResumeAnalytics(Resume $resume): array
{
return [
'completion_percentage' => $resume->completion_percentage,
'sections_completed' => $this->getCompletedSections($resume),
'total_sections' => 7,
'word_count' => $this->calculateWordCount($resume),
'last_updated' => $resume->updated_at,
'views' => 0, // Implement view tracking if needed
];
}
/**
* Get completed sections count
*/
protected function getCompletedSections(Resume $resume): int
{
$sections = ['personal_info', 'professional_summary', 'work_experiences',
'education', 'skills', 'languages', 'certifications'];
$completed = 0;
foreach ($sections as $section) {
if ($resume->isSectionCompleted($section)) {
$completed++;
}
}
return $completed;
}
/**
* Calculate total word count in resume
*/
protected function calculateWordCount(Resume $resume): int
{
$wordCount = 0;
// Professional summary
if ($resume->professional_summary) {
$wordCount += str_word_count(strip_tags($resume->professional_summary));
}
// Work experiences
if ($resume->work_experiences) {
foreach ($resume->work_experiences as $experience) {
$wordCount += str_word_count(strip_tags($experience['description'] ?? ''));
}
}
// Education
if ($resume->education) {
foreach ($resume->education as $education) {
$wordCount += str_word_count(strip_tags($education['description'] ?? ''));
}
}
// Projects
if ($resume->projects) {
foreach ($resume->projects as $project) {
$wordCount += str_word_count(strip_tags($project['description'] ?? ''));
}
}
return $wordCount;
}
/**
* Check if resume should be marked as completed
*/
protected function shouldMarkAsCompleted(array $resumeData): bool
{
$requiredSections = ['personal_info', 'work_experiences', 'education'];
foreach ($requiredSections as $section) {
if (empty($resumeData[$section])) {
return false;
}
}
return true;
}
/**
* Get default resume settings
*/
protected function getDefaultSettings(): array
{
return [
'theme' => 'light',
'font_family' => 'Roboto',
'font_size' => 14,
'color_scheme' => 'blue',
'show_photo' => false,
'show_references' => true,
'show_certifications' => true,
'show_projects' => true,
'section_order' => [
'personal_info',
'professional_summary',
'work_experiences',
'education',
'skills',
'languages',
'certifications',
'projects',
'references'
]
];
}
/**
* Get resume statistics for user
*/
public function getUserResumeStats(int $userId): array
{
$resumes = $this->getUserResumes($userId);
return [
'total_resumes' => $resumes->count(),
'completed_resumes' => $resumes->where('is_completed', true)->count(),
'public_resumes' => $resumes->where('is_public', true)->count(),
'avg_completion' => $resumes->avg('completion_percentage') ?? 0,
'last_updated' => $resumes->max('updated_at'),
];
}
}