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

260
app/Models/Resume.php Normal file
View File

@@ -0,0 +1,260 @@
<?php
/**
* Resume Model
* Professional Resume Builder - Resume Entity
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Resume Model
* Represents user resumes with all personal and professional information
*/
class Resume extends Model
{
use HasFactory, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'user_id',
'title',
'template_id',
'slug',
'personal_info',
'professional_summary',
'work_experiences',
'education',
'skills',
'languages',
'certifications',
'projects',
'references',
'custom_sections',
'settings',
'is_active',
'is_completed',
'is_public',
'public_url',
'created_at',
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'personal_info' => 'array',
'work_experiences' => 'array',
'education' => 'array',
'skills' => 'array',
'languages' => 'array',
'certifications' => 'array',
'projects' => 'array',
'references' => 'array',
'custom_sections' => 'array',
'settings' => 'array',
'is_active' => 'boolean',
'is_completed' => 'boolean',
'is_public' => 'boolean',
];
/**
* Get the user that owns the resume.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Get the resume's public URL.
*/
public function getPublicUrlAttribute(): ?string
{
if ($this->is_public && $this->public_url) {
return route('public.resume', $this->public_url);
}
return null;
}
/**
* Get the resume's preview URL.
*/
public function getPreviewUrlAttribute(): string
{
return route('resume-builder.preview', $this);
}
/**
* Get the resume's edit URL.
*/
public function getEditUrlAttribute(): string
{
return route('resume-builder.edit', $this);
}
/**
* Get the resume's PDF download URL.
*/
public function getPdfUrlAttribute(): string
{
return route('resume-builder.download-pdf', $this);
}
/**
* Calculate completion percentage of the resume.
*/
public function getCompletionPercentageAttribute(): int
{
$sections = [
'personal_info' => 25,
'professional_summary' => 10,
'work_experiences' => 30,
'education' => 15,
'skills' => 10,
'languages' => 5,
'certifications' => 5,
];
$completed = 0;
foreach ($sections as $section => $weight) {
if ($this->isSectionCompleted($section)) {
$completed += $weight;
}
}
return min(100, $completed);
}
/**
* Check if a specific section is completed.
*/
public function isSectionCompleted(string $section): bool
{
$data = $this->$section;
if (empty($data)) {
return false;
}
switch ($section) {
case 'personal_info':
$required = ['first_name', 'last_name', 'email', 'phone'];
foreach ($required as $field) {
if (empty($data[$field] ?? null)) {
return false;
}
}
return true;
case 'professional_summary':
return !empty($data) && strlen($data) >= 50;
case 'work_experiences':
return is_array($data) && count($data) > 0 && !empty($data[0]['company'] ?? null);
case 'education':
return is_array($data) && count($data) > 0 && !empty($data[0]['institution'] ?? null);
case 'skills':
return is_array($data) && count($data) > 0;
default:
return !empty($data);
}
}
/**
* Generate a unique slug for the resume.
*/
public function generateSlug(): string
{
$baseSlug = str()->slug($this->title);
$slug = $baseSlug;
$counter = 1;
while (static::where('slug', $slug)->where('id', '!=', $this->id)->exists()) {
$slug = $baseSlug . '-' . $counter;
$counter++;
}
return $slug;
}
/**
* Generate a unique public URL for the resume.
*/
public function generatePublicUrl(): string
{
return str()->random(32);
}
/**
* Mark the resume as completed.
*/
public function markAsCompleted(): void
{
$this->update(['is_completed' => true]);
}
/**
* Scope a query to only include active resumes.
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope a query to only include completed resumes.
*/
public function scopeCompleted($query)
{
return $query->where('is_completed', true);
}
/**
* Scope a query to only include public resumes.
*/
public function scopePublic($query)
{
return $query->where('is_public', true);
}
/**
* Boot the model.
*/
protected static function boot()
{
parent::boot();
static::creating(function ($resume) {
if (empty($resume->slug)) {
$resume->slug = $resume->generateSlug();
}
});
static::updating(function ($resume) {
if ($resume->isDirty('title')) {
$resume->slug = $resume->generateSlug();
}
});
}
}

191
app/Models/User.php Normal file
View File

@@ -0,0 +1,191 @@
<?php
/**
* User Model
* Professional Resume Builder - User Entity
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
/**
* User Model
* Represents application users with authentication and profile features
*/
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'first_name',
'last_name',
'email',
'password',
'phone',
'date_of_birth',
'gender',
'avatar',
'bio',
'website',
'linkedin',
'github',
'twitter',
'preferences',
'newsletter_subscribed',
'last_login_at',
'last_login_ip',
'status',
'suspended_at',
'suspension_reason',
'login_attempts',
'locked_until',
'locale',
'timezone',
'two_factor_enabled',
'two_factor_secret',
'two_factor_recovery_codes',
'profile_completed_at'
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
'date_of_birth' => 'date',
'suspended_at' => 'datetime',
'locked_until' => 'datetime',
'profile_completed_at' => 'datetime',
'newsletter_subscribed' => 'boolean',
'two_factor_enabled' => 'boolean',
'preferences' => 'array',
'two_factor_recovery_codes' => 'array',
'password' => 'hashed',
];
}
/**
* Get the user's full name.
*/
public function getFullNameAttribute(): string
{
return "{$this->first_name} {$this->last_name}";
}
/**
* Get the user's initials.
*/
public function getInitialsAttribute(): string
{
return strtoupper(substr($this->first_name, 0, 1) . substr($this->last_name, 0, 1));
}
/**
* Get the user's avatar URL.
*/
public function getAvatarUrlAttribute(): string
{
if ($this->avatar) {
return asset('storage/avatars/' . $this->avatar);
}
return "https://ui-avatars.com/api/?name={$this->initials}&background=667eea&color=fff&size=200";
}
/**
* Check if user has completed their profile.
*/
public function hasCompletedProfile(): bool
{
$requiredFields = ['first_name', 'last_name', 'email', 'phone', 'city', 'country'];
foreach ($requiredFields as $field) {
if (empty($this->$field)) {
return false;
}
}
return true;
}
/**
* Get the user's resumes.
*/
public function resumes(): HasMany
{
return $this->hasMany(Resume::class)->orderBy('updated_at', 'desc');
}
/**
* Get the user's active resumes.
*/
public function activeResumes(): HasMany
{
return $this->resumes()->where('is_active', true);
}
/**
* Get the user's completed resumes.
*/
public function completedResumes(): HasMany
{
return $this->resumes()->where('is_completed', true);
}
/**
* Update user's last login information.
*/
public function updateLastLogin(string $ipAddress): void
{
$this->update([
'last_login_at' => now(),
'last_login_ip' => $ipAddress,
]);
}
/**
* Scope a query to only include active users.
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope a query to only include verified users.
*/
public function scopeVerified($query)
{
return $query->whereNotNull('email_verified_at');
}
}