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,117 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
/**
* API Authentication Controller
* Handles API authentication endpoints
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-09
* @location Made in Germany 🇩🇪
*/
class AuthController extends Controller
{
/**
* Handle API login
*/
public function login(Request $request): JsonResponse
{
return response()->json([
'message' => 'API login not implemented yet',
'status' => 'error'
], 501);
}
/**
* Handle API registration
*/
public function register(Request $request): JsonResponse
{
return response()->json([
'message' => 'API registration not implemented yet',
'status' => 'error'
], 501);
}
/**
* Handle forgot password
*/
public function forgotPassword(Request $request): JsonResponse
{
return response()->json([
'message' => 'API forgot password not implemented yet',
'status' => 'error'
], 501);
}
/**
* Handle password reset
*/
public function resetPassword(Request $request): JsonResponse
{
return response()->json([
'message' => 'API password reset not implemented yet',
'status' => 'error'
], 501);
}
/**
* Refresh token
*/
public function refresh(Request $request): JsonResponse
{
return response()->json([
'message' => 'API token refresh not implemented yet',
'status' => 'error'
], 501);
}
/**
* Handle logout
*/
public function logout(Request $request): JsonResponse
{
return response()->json([
'message' => 'API logout not implemented yet',
'status' => 'error'
], 501);
}
/**
* Get current user
*/
public function me(Request $request): JsonResponse
{
return response()->json([
'message' => 'API user profile not implemented yet',
'status' => 'error'
], 501);
}
/**
* Update user profile
*/
public function updateProfile(Request $request): JsonResponse
{
return response()->json([
'message' => 'API profile update not implemented yet',
'status' => 'error'
], 501);
}
/**
* Update password
*/
public function updatePassword(Request $request): JsonResponse
{
return response()->json([
'message' => 'API password update not implemented yet',
'status' => 'error'
], 501);
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
/**
* Base API Controller for V1
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
abstract class ApiController extends Controller
{
/**
* Success response
*/
protected function successResponse($data = null, string $message = 'Success', int $code = 200): JsonResponse
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $code);
}
/**
* Error response
*/
protected function errorResponse(string $message = 'Error', int $code = 400, $errors = null): JsonResponse
{
$response = [
'success' => false,
'message' => $message,
];
if ($errors) {
$response['errors'] = $errors;
}
return response()->json($response, $code);
}
/**
* Validation error response
*/
protected function validationErrorResponse($errors): JsonResponse
{
return $this->errorResponse('Validation failed', 422, $errors);
}
}

View File

@@ -0,0 +1,586 @@
<?php
/**
* Authentication Controller
* Professional Resume Builder - Enterprise Auth System
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Http\Requests\Auth\RegisterRequest;
use App\Http\Requests\Auth\UpdateProfileRequest;
use App\Interfaces\UserRepositoryInterface;
use App\Services\AuthService;
use App\Services\ProfileCompletionService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use Exception;
/**
* Authentication Controller
* Handles user authentication, registration, and session management with repository pattern
*/
class AuthController extends Controller
{
/**
* User repository instance
*/
private UserRepositoryInterface $userRepository;
/**
* AuthService instance
*/
private AuthService $authService;
/**
* Profile completion service instance
*/
private ProfileCompletionService $profileCompletionService;
/**
* Maximum login attempts before lockout
*/
private const MAX_LOGIN_ATTEMPTS = 5;
/**
* Account lockout duration in minutes
*/
private const LOCKOUT_DURATION = 15;
/**
* Create a new controller instance with dependency injection
*/
public function __construct(
UserRepositoryInterface $userRepository,
AuthService $authService,
ProfileCompletionService $profileCompletionService
) {
$this->userRepository = $userRepository;
$this->authService = $authService;
$this->profileCompletionService = $profileCompletionService;
$this->middleware('guest')->except(['logout', 'profile', 'updateProfile', 'activity']);
$this->middleware('auth')->only(['logout', 'profile', 'updateProfile', 'activity']);
}
/**
* Show the login form
*/
public function showLoginForm(): View
{
return view('auth.login', [
'title' => 'Sign In - Professional Resume Builder',
'description' => 'Access your professional resume builder account'
]);
}
/**
* Handle a login request with enhanced maintainability
*
* @param LoginRequest $request
* @return RedirectResponse
*/
public function login(LoginRequest $request): RedirectResponse
{
try {
$credentials = $request->validated();
$loginResult = $this->authService->attemptLogin($credentials);
return $this->handleLoginResult($loginResult, $request);
} catch (ValidationException $e) {
return $this->handleLoginValidationError($e, $request);
} catch (Exception $e) {
return $this->handleLoginSystemError($e, $request);
}
}
/**
* Handle the result of a login attempt
*
* @param array $loginResult
* @param LoginRequest $request
* @return RedirectResponse
*/
private function handleLoginResult(array $loginResult, LoginRequest $request): RedirectResponse
{
if ($loginResult['success']) {
return $this->handleSuccessfulLogin($loginResult, $request);
}
return $this->handleFailedLogin($loginResult, $request);
}
/**
* Handle successful login response
*
* @param array $loginResult
* @param LoginRequest $request
* @return RedirectResponse
*/
private function handleSuccessfulLogin(array $loginResult, LoginRequest $request): RedirectResponse
{
$request->session()->regenerate();
Log::info('User logged in successfully', [
'user_id' => $loginResult['user']->id,
'email' => $loginResult['user']->email,
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'login_method' => 'standard'
]);
$welcomeMessage = $this->getWelcomeMessage($loginResult['user']);
$redirectUrl = $this->getPostLoginRedirectUrl($loginResult['user']);
return redirect()->intended($redirectUrl)->with('success', $welcomeMessage);
}
/**
* Handle failed login response
*
* @param array $loginResult
* @param LoginRequest $request
* @return RedirectResponse
*/
private function handleFailedLogin(array $loginResult, LoginRequest $request): RedirectResponse
{
$errorMessage = $this->getLoginErrorMessage($loginResult['reason']);
Log::warning('Login attempt failed', [
'email' => $request->input('email'),
'reason' => $loginResult['reason'],
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent()
]);
return back()
->withErrors(['email' => $errorMessage])
->onlyInput('email');
}
/**
* Handle validation errors during login
*
* @param ValidationException $e
* @param LoginRequest $request
* @return RedirectResponse
*/
private function handleLoginValidationError(ValidationException $e, LoginRequest $request): RedirectResponse
{
Log::warning('Login validation error', [
'email' => $request->input('email'),
'errors' => $e->errors(),
'ip_address' => $request->ip()
]);
return back()
->withErrors($e->errors())
->onlyInput('email');
}
/**
* Handle system errors during login
*
* @param Exception $e
* @param LoginRequest $request
* @return RedirectResponse
*/
private function handleLoginSystemError(Exception $e, LoginRequest $request): RedirectResponse
{
Log::error('Login system error', [
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
'email' => $request->input('email'),
'ip_address' => $request->ip(),
'timestamp' => now()
]);
return back()
->withErrors(['email' => 'A system error occurred. Please try again or contact support.'])
->onlyInput('email');
}
/**
* Get personalized welcome message
*
* @param User $user
* @return string
*/
private function getWelcomeMessage(User $user): string
{
$timeOfDay = $this->getTimeOfDay();
$firstName = $user->first_name ?? 'there';
return "Good {$timeOfDay}, {$firstName}! Welcome back to your professional resume builder.";
}
/**
* Get post-login redirect URL based on user profile and activity state
*
* Intelligently determines the most appropriate landing page based on:
* - Profile completion percentage (incomplete profiles → profile page)
* - Resume creation status (no resumes → dashboard for guidance)
* - Default → main dashboard
*
* @param User $user The authenticated user
* @return string The route name for redirection
*/
private function getPostLoginRedirectUrl(User $user): string
{
$profileCompletion = $this->profileCompletionService->calculateCompletion($user);
if ($profileCompletion < 50) {
return route('profile');
}
$userStats = $this->userRepository->getUserStatistics($user->id);
if (($userStats['resumes_count'] ?? 0) === 0) {
return route('dashboard');
}
return route('dashboard');
}
/**
* Get appropriate error message based on failure reason
*
* @param string $reason
* @return string
*/
private function getLoginErrorMessage(string $reason): string
{
switch ($reason) {
case 'account_locked':
return 'Account is temporarily locked due to too many failed attempts. Please try again later.';
case 'account_inactive':
return 'Your account has been deactivated. Please contact support.';
case 'invalid_credentials':
return 'The provided credentials do not match our records.';
case 'too_many_attempts':
return 'Too many failed attempts. Account has been locked for security.';
case 'rate_limited':
return 'Too many login attempts. Please wait before trying again.';
default:
return 'Login failed. Please check your credentials and try again.';
}
}
/**
* Get time of day greeting
*
* @return string
*/
private function getTimeOfDay(): string
{
$hour = now()->hour;
if ($hour < 12) {
return 'morning';
} elseif ($hour < 17) {
return 'afternoon';
} else {
return 'evening';
}
}
/**
* Show the registration form
*/
public function showRegistrationForm(): View
{
return view('auth.register', [
'title' => 'Create Account - Professional Resume Builder',
'description' => 'Join thousands of professionals creating outstanding resumes'
]);
}
/**
* Handle a registration request
*/
public function register(RegisterRequest $request): RedirectResponse
{
try {
$userData = $request->validated();
$user = $this->authService->createUser($userData);
if ($user) {
Auth::login($user);
// Log successful registration
logger()->info('User registered successfully', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => $request->ip(),
'user_agent' => $request->userAgent()
]);
return redirect()->route('dashboard')
->with('success', 'Welcome! Your account has been created successfully.');
}
return back()->withErrors([
'email' => 'Unable to create account. Please try again.',
])->withInput();
} catch (\Exception $e) {
logger()->error('Registration error', [
'error' => $e->getMessage(),
'email' => $request->input('email'),
'ip' => $request->ip()
]);
return back()->withErrors([
'email' => 'An error occurred during registration. Please try again.',
])->withInput();
}
}
/**
* Handle logout request
*/
public function logout(Request $request): RedirectResponse
{
// Log the logout
logger()->info('User logged out', [
'user_id' => Auth::id(),
'ip' => $request->ip()
]);
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('login')
->with('success', 'You have been successfully logged out.');
}
/**
* Show user profile with comprehensive analytics
*/
public function profile(): View
{
try {
$user = Auth::user();
// Get user statistics from repository
$userStats = $this->userRepository->getUserStatistics($user->id);
// Get profile completion analysis from service
$profileCompletion = $this->profileCompletionService->calculateCompletion($user);
$profileAnalysis = $this->profileCompletionService->getDetailedAnalysis($user);
// Prepare view data
$viewData = [
'title' => 'Profile Settings - Professional Resume Builder',
'user' => $user,
'userStats' => $userStats,
'profileCompletion' => $profileCompletion,
'profileAnalysis' => $profileAnalysis,
'accountSecurity' => [
'two_factor_enabled' => $user->two_factor_secret !== null,
'email_verified' => $user->email_verified_at !== null,
'is_locked' => $this->userRepository->isAccountLocked($user->email)
]
];
// Log profile view for analytics
Log::info('User profile viewed', [
'user_id' => $user->id,
'profile_completion' => $profileCompletion,
'ip_address' => request()->ip(),
'timestamp' => now()
]);
return view('auth.profile', $viewData);
} catch (Exception $e) {
Log::error('Error loading user profile', [
'error_message' => $e->getMessage(),
'user_id' => Auth::id(),
'ip_address' => request()->ip(),
'timestamp' => now()
]);
// Fallback with minimal data
return view('auth.profile', [
'title' => 'Profile Settings',
'user' => Auth::user(),
'userStats' => [],
'profileCompletion' => 0,
'error' => 'Unable to load profile data completely'
]);
}
}
/**
* Update user profile using professional form request validation
*
* Handles comprehensive profile updates with:
* - Repository pattern for data persistence
* - Structured logging for security audit trails
* - Real-time profile completion calculation
* - User-friendly completion status messaging
*
* @param UpdateProfileRequest $request Validated profile update data
* @return RedirectResponse Response with success message or error state
* @throws Exception When profile update fails due to system errors
*/
public function updateProfile(UpdateProfileRequest $request): RedirectResponse
{
try {
$user = Auth::user();
$updateData = $request->validated();
$updatedUser = $this->userRepository->update($user->id, $updateData);
Log::info('User profile updated successfully', [
'user_id' => $user->id,
'updated_fields' => array_keys($updateData),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'timestamp' => now()
]);
$completionPercentage = $this->profileCompletionService
->calculateCompletion($updatedUser);
$message = "Profile updated successfully! ";
if ($completionPercentage < 100) {
$message .= "Your profile is {$completionPercentage}% complete.";
} else {
$message .= "Your profile is now 100% complete!";
}
return back()->with('success', $message);
} catch (Exception $e) {
Log::error('Profile update failed', [
'error_message' => $e->getMessage(),
'error_trace' => $e->getTraceAsString(),
'user_id' => Auth::id(),
'request_data' => $request->except(['password', 'password_confirmation']),
'ip_address' => $request->ip(),
'timestamp' => now()
]);
return back()
->withErrors(['error' => 'Unable to update profile. Please try again.'])
->withInput();
}
}
/**
* Retrieve comprehensive user activity and analytics data
*
* Provides detailed activity analytics including:
* - User profile and authentication information
* - Login activity patterns and security metrics
* - Profile completion analysis with detailed breakdown
* - Account security status and recommendations
* - Usage statistics (resumes, templates, exports)
*
* @return JsonResponse Structured activity data with metadata
* @throws Exception When analytics data retrieval fails
*/
public function activity(): JsonResponse
{
try {
$user = Auth::user();
$userStats = $this->userRepository->getUserStatistics($user->id);
$profileCompletion = $this->profileCompletionService
->calculateCompletion($user);
$profileAnalysis = $this->profileCompletionService
->getDetailedAnalysis($user);
$activityData = [
'user_info' => [
'id' => $user->id,
'name' => $user->first_name . ' ' . $user->last_name,
'email' => $user->email,
'member_since' => $user->created_at->format('F Y'),
'account_status' => $user->status ?? 'active'
],
'login_activity' => [
'last_login' => $user->last_login_at ? $user->last_login_at->format('Y-m-d H:i:s') : null,
'last_login_ip' => $user->last_login_ip,
'total_logins' => $userStats['total_logins'] ?? 0,
'failed_attempts' => $user->login_attempts ?? 0
],
'profile_metrics' => [
'completion_percentage' => $profileCompletion,
'completed_fields' => $profileAnalysis['completed_fields'],
'missing_fields' => $profileAnalysis['missing_fields'],
'completion_score' => $profileAnalysis['weighted_score']
],
'account_security' => [
'two_factor_enabled' => $user->two_factor_secret !== null,
'email_verified' => $user->email_verified_at !== null,
'password_updated' => $user->password_updated_at ? $user->password_updated_at->format('Y-m-d') : null,
'is_locked' => $this->userRepository->isAccountLocked($user->email)
],
'activity_summary' => [
'resumes_created' => $userStats['resumes_count'] ?? 0,
'templates_used' => $userStats['templates_used'] ?? 0,
'exports_generated' => $userStats['exports_count'] ?? 0,
'profile_views' => $userStats['profile_views'] ?? 0
]
];
Log::info('User activity data requested', [
'user_id' => $user->id,
'ip_address' => request()->ip(),
'timestamp' => now()
]);
return response()->json([
'success' => true,
'data' => $activityData,
'meta' => [
'generated_at' => now()->toISOString(),
'version' => '1.0'
]
]);
} catch (Exception $e) {
Log::error('Failed to retrieve user activity data', [
'error_message' => $e->getMessage(),
'user_id' => Auth::id(),
'ip_address' => request()->ip(),
'timestamp' => now()
]);
return response()->json([
'success' => false,
'message' => 'Unable to retrieve activity data',
'error' => app()->environment('local') ? $e->getMessage() : 'Internal server error'
], 500);
}
}
/**
* Show forgot password form
*/
public function showForgotPasswordForm(): View
{
return view('auth.forgot-password', [
'title' => 'Reset Password - Professional Resume Builder',
'description' => 'Enter your email to receive password reset instructions'
]);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Base Controller Class
* Professional Resume Builder - Laravel Application
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
/**
* Base Controller
* Parent class for all application controllers
*/
abstract class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* Dashboard Controller
* Professional Resume Builder - Main Dashboard
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Http\Controllers;
use App\Interfaces\UserRepositoryInterface;
use App\Interfaces\ResumeRepositoryInterface;
use App\Services\ResumeService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
/**
* Dashboard Controller
* Handles the main dashboard functionality with repository pattern
*/
class DashboardController extends Controller
{
/**
* User repository instance
*/
protected UserRepositoryInterface $userRepository;
/**
* Resume repository instance
*/
protected ResumeRepositoryInterface $resumeRepository;
/**
* Resume service instance
*/
protected ResumeService $resumeService;
/**
* Create a new controller instance with dependency injection
*/
public function __construct(
UserRepositoryInterface $userRepository,
ResumeRepositoryInterface $resumeRepository,
ResumeService $resumeService
) {
$this->userRepository = $userRepository;
$this->resumeRepository = $resumeRepository;
$this->resumeService = $resumeService;
$this->middleware('auth');
}
/**
* Show the application dashboard
*/
public function index(): View
{
$user = Auth::user();
// Get user's resumes using repository
$resumes = $this->resumeRepository->getUserResumes($user->id);
// Get resume statistics using repository
$resumeStats = $this->resumeRepository->getResumeStatistics($user->id);
// Get recent resumes (last 5)
$recentResumes = $this->resumeRepository
->orderBy('updated_at', 'desc')
->limit(5)
->getUserResumes($user->id);
// Calculate profile completion
$profileCompletion = $this->calculateProfileCompletion($user);
return view('dashboard.index', [
'title' => 'Dashboard - Professional Resume Builder',
'user' => $user,
'resumes' => $resumes,
'resumeStats' => $resumeStats,
'recentResumes' => $recentResumes,
'profileCompletion' => $profileCompletion,
'stats' => [
'total_resumes' => $resumeStats['total_resumes'],
'published_resumes' => $resumeStats['published_resumes'],
'draft_resumes' => $resumeStats['draft_resumes'],
'completed_resumes' => $resumeStats['completed_resumes'],
'avg_completion' => $resumeStats['avg_completion'],
'total_views' => $resumeStats['total_views'],
'total_downloads' => $resumeStats['total_downloads'],
'profile_completion' => $profileCompletion
]
]);
}
/**
* Calculate profile completion percentage
*/
private function calculateProfileCompletion($user): int
{
$requiredFields = [
'first_name', 'last_name', 'email', 'phone',
'bio', 'website', 'linkedin'
];
$completed = 0;
foreach ($requiredFields as $field) {
if (!empty($user->$field)) {
$completed++;
}
}
return (int) (($completed / count($requiredFields)) * 100);
}
/**
* Get dashboard analytics data
*/
public function analytics(): \Illuminate\Http\JsonResponse
{
$user = Auth::user();
$analytics = [
'user_stats' => $this->userRepository->getUserStatistics(),
'resume_stats' => $this->resumeRepository->getResumeStatistics($user->id),
'recent_activity' => $this->getRecentActivity($user->id),
];
return response()->json($analytics);
}
/**
* Get recent user activity
*/
private function getRecentActivity(int $userId): array
{
$recentResumes = $this->resumeRepository
->orderBy('updated_at', 'desc')
->limit(10)
->getUserResumes($userId);
return $recentResumes->map(function ($resume) {
return [
'id' => $resume->id,
'title' => $resume->title,
'action' => 'updated',
'timestamp' => $resume->updated_at,
'status' => $resume->status,
];
})->toArray();
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
class HomeController extends Controller
{
/**
* Display the homepage.
*/
public function index(): View|RedirectResponse
{
// If user is authenticated, redirect to dashboard
if (Auth::check()) {
return redirect()->route('dashboard');
}
// If not authenticated, redirect to login
return redirect()->route('login');
}
/**
* Display the features page.
*/
public function features(): View
{
return view('home.features');
}
/**
* Display the pricing page.
*/
public function pricing(): View
{
return view('home.pricing');
}
/**
* Display the help page.
*/
public function help(): View
{
return view('home.help');
}
/**
* Display the contact page.
*/
public function contact(): View
{
return view('home.contact');
}
/**
* Handle contact form submission.
*/
public function submitContact(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|max:255',
'subject' => 'required|string|max:255',
'message' => 'required|string',
]);
// Handle contact form logic here
// You can send email, save to database, etc.
return redirect()->route('contact')->with('success', 'Your message has been sent successfully!');
}
/**
* Display the privacy policy page.
*/
public function privacy(): View
{
return view('home.privacy');
}
/**
* Display the terms of service page.
*/
public function terms(): View
{
return view('home.terms');
}
/**
* Display the support page.
*/
public function support(): View
{
return view('home.support');
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rules\Password;
class ProfileController extends Controller
{
/**
* Display the user's profile.
*/
public function show(): View
{
$user = Auth::user();
return view('profile.show', compact('user'));
}
/**
* Show the form for editing the user's profile.
*/
public function edit(): View
{
$user = Auth::user();
return view('profile.edit', compact('user'));
}
/**
* Update the user's profile information.
*/
public function update(Request $request): RedirectResponse
{
$user = Auth::user();
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|max:255|unique:users,email,' . $user->id,
'phone' => 'nullable|string|max:20',
'bio' => 'nullable|string|max:1000',
]);
$user->update($request->only(['name', 'email', 'phone', 'bio']));
return redirect()->route('profile.show')->with('success', 'Profile updated successfully!');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validate([
'password' => 'required|current_password',
]);
$user = Auth::user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/')->with('success', 'Your account has been deleted.');
}
/**
* Show the settings page.
*/
public function settings(): View
{
$user = Auth::user();
return view('profile.settings', compact('user'));
}
/**
* Update the user's password.
*/
public function updatePassword(Request $request): RedirectResponse
{
$request->validate([
'current_password' => 'required|current_password',
'password' => ['required', 'confirmed', Password::defaults()],
]);
Auth::user()->update([
'password' => Hash::make($request->password),
]);
return redirect()->route('profile.settings')->with('success', 'Password updated successfully!');
}
/**
* Update user preferences.
*/
public function updatePreferences(Request $request): RedirectResponse
{
$request->validate([
'theme' => 'required|in:light,dark,auto',
'notifications_email' => 'boolean',
'notifications_browser' => 'boolean',
'language' => 'required|string|max:5',
]);
$user = Auth::user();
// If your User model has a preferences JSON column
$preferences = $user->preferences ?? [];
$preferences['theme'] = $request->theme;
$preferences['notifications_email'] = $request->boolean('notifications_email');
$preferences['notifications_browser'] = $request->boolean('notifications_browser');
$preferences['language'] = $request->language;
$user->update(['preferences' => $preferences]);
return redirect()->route('profile.settings')->with('success', 'Preferences updated successfully!');
}
/**
* Update user avatar.
*/
public function updateAvatar(Request $request): RedirectResponse
{
$request->validate([
'avatar' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
$user = Auth::user();
// Delete old avatar if exists
if ($user->avatar && Storage::exists('public/' . $user->avatar)) {
Storage::delete('public/' . $user->avatar);
}
// Store new avatar
$avatarPath = $request->file('avatar')->store('avatars', 'public');
$user->update(['avatar' => $avatarPath]);
return redirect()->route('profile.settings')->with('success', 'Avatar updated successfully!');
}
/**
* Show profile completion status.
*/
public function completion(): View
{
$user = Auth::user();
// Calculate completion percentage
$fields = ['name', 'email', 'phone', 'bio', 'avatar'];
$completedFields = 0;
foreach ($fields as $field) {
if (!empty($user->$field)) {
$completedFields++;
}
}
$completionPercentage = round(($completedFields / count($fields)) * 100);
return view('profile.completion', compact('user', 'completionPercentage'));
}
}

View File

@@ -0,0 +1,356 @@
<?php
/**
* Resume Builder Controller
* Professional Resume Builder - Resume Creation and Management
*
* @author David Valera Melendez <david@valera-melendez.de>
* @created 2025-08-08
* @location Made in Germany 🇩🇪
*/
namespace App\Http\Controllers;
use App\Http\Requests\Resume\StoreResumeRequest;
use App\Http\Requests\Resume\UpdateResumeRequest;
use App\Models\Resume;
use App\Interfaces\ResumeRepositoryInterface;
use App\Interfaces\UserRepositoryInterface;
use App\Services\ResumeService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
/**
* Resume Builder Controller
* Handles resume creation, editing, and management with repository pattern
*/
class ResumeBuilderController extends Controller
{
/**
* Resume repository instance
*/
protected ResumeRepositoryInterface $resumeRepository;
/**
* User repository instance
*/
protected UserRepositoryInterface $userRepository;
/**
* ResumeService instance
*/
protected ResumeService $resumeService;
/**
* Create a new controller instance with dependency injection
*/
public function __construct(
ResumeRepositoryInterface $resumeRepository,
UserRepositoryInterface $userRepository,
ResumeService $resumeService
) {
$this->resumeRepository = $resumeRepository;
$this->userRepository = $userRepository;
$this->resumeService = $resumeService;
$this->middleware('auth');
}
/**
* Display a listing of user's resumes
*/
public function index(Request $request): View
{
$userId = Auth::id();
// Get paginated resumes using repository
$resumes = $this->resumeRepository->getPaginatedUserResumes($userId, 12);
// Get resume statistics using repository
$resumeStats = $this->resumeRepository->getResumeStatistics($userId);
return view('resume-builder.index', [
'title' => 'My Resumes - Resume Builder',
'resumes' => $resumes,
'resumeStats' => $resumeStats
]);
}
/**
* Show the form for creating a new resume
*/
public function create(): View
{
$userId = Auth::id();
// Check if user can create more resumes
if (!$this->resumeRepository->canUserCreateMoreResumes($userId, 10)) {
return redirect()->route('resume-builder.index')
->with('error', 'You have reached the maximum number of resumes allowed.');
}
$templates = $this->resumeService->getAvailableTemplates();
return view('resume-builder.create', [
'title' => 'Create New Resume',
'templates' => $templates
]);
}
/**
* Store a newly created resume
*/
public function store(StoreResumeRequest $request): RedirectResponse
{
try {
$userId = Auth::id();
// Check if user can create more resumes
if (!$this->resumeRepository->canUserCreateMoreResumes($userId, 10)) {
return back()->withErrors([
'title' => 'You have reached the maximum number of resumes allowed.'
])->withInput();
}
$resumeData = $request->validated();
$resumeData['user_id'] = $userId;
// Create resume using repository
$resume = $this->resumeRepository->create($resumeData);
// Update completion percentage
$this->resumeRepository->updateCompletionPercentage($resume->id);
logger()->info('Resume created', [
'user_id' => $userId,
'resume_id' => $resume->id,
'title' => $resume->title
]);
return redirect()->route('resume-builder.edit', $resume)
->with('success', 'Resume created successfully! Start building your professional CV.');
} catch (\Exception $e) {
logger()->error('Resume creation error', [
'error' => $e->getMessage(),
'user_id' => Auth::id()
]);
return back()->withErrors([
'title' => 'Unable to create resume. Please try again.'
])->withInput();
}
}
/**
* Show the form for editing the specified resume
*/
public function edit(Resume $resume): View
{
$this->authorize('update', $resume);
return view('resume-builder.edit', [
'title' => 'Edit Resume - ' . $resume->title,
'resume' => $resume,
'templates' => $this->resumeService->getAvailableTemplates()
]);
}
/**
* Update the specified resume
*/
public function update(UpdateResumeRequest $request, Resume $resume): RedirectResponse
{
$this->authorize('update', $resume);
try {
$resumeData = $request->validated();
// Update resume using repository
$this->resumeRepository->update($resume->id, $resumeData);
// Update completion percentage
$this->resumeRepository->updateCompletionPercentage($resume->id);
logger()->info('Resume updated', [
'user_id' => Auth::id(),
'resume_id' => $resume->id
]);
return back()->with('success', 'Resume updated successfully!');
} catch (\Exception $e) {
logger()->error('Resume update error', [
'error' => $e->getMessage(),
'user_id' => Auth::id(),
'resume_id' => $resume->id
]);
return back()->withErrors([
'title' => 'Unable to update resume. Please try again.'
])->withInput();
}
}
/**
* Display the specified resume preview
*/
public function preview(Resume $resume): View
{
$this->authorize('view', $resume);
// Increment view count using repository
$this->resumeRepository->incrementViewCount($resume->id);
return view('resume-builder.preview', [
'title' => 'Preview - ' . $resume->title,
'resume' => $resume
]);
}
/**
* Remove the specified resume
*/
public function destroy(Resume $resume): RedirectResponse
{
$this->authorize('delete', $resume);
try {
// Delete resume using repository
$this->resumeRepository->delete($resume->id);
logger()->info('Resume deleted', [
'user_id' => Auth::id(),
'resume_id' => $resume->id,
'title' => $resume->title
]);
return redirect()->route('resume-builder.index')
->with('success', 'Resume deleted successfully.');
} catch (\Exception $e) {
logger()->error('Resume deletion error', [
'error' => $e->getMessage(),
'user_id' => Auth::id(),
'resume_id' => $resume->id
]);
return back()->withErrors([
'error' => 'Unable to delete resume. Please try again.'
]);
}
}
/**
* Download resume as PDF
*/
public function downloadPdf(Resume $resume)
{
$this->authorize('view', $resume);
try {
// Increment download count using repository
$this->resumeRepository->incrementDownloadCount($resume->id);
return $this->resumeService->generatePdf($resume);
} catch (\Exception $e) {
logger()->error('PDF generation error', [
'error' => $e->getMessage(),
'user_id' => Auth::id(),
'resume_id' => $resume->id
]);
return back()->withErrors([
'error' => 'Unable to generate PDF. Please try again.'
]);
}
}
/**
* Duplicate an existing resume
*/
public function duplicate(Resume $resume): RedirectResponse
{
$this->authorize('view', $resume);
try {
$userId = Auth::id();
// Check if user can create more resumes
if (!$this->resumeRepository->canUserCreateMoreResumes($userId, 10)) {
return back()->withErrors([
'error' => 'You have reached the maximum number of resumes allowed.'
]);
}
$newTitle = $resume->title . ' (Copy)';
$duplicatedResume = $this->resumeRepository->duplicateResume($resume->id, $userId, $newTitle);
logger()->info('Resume duplicated', [
'user_id' => $userId,
'original_resume_id' => $resume->id,
'new_resume_id' => $duplicatedResume->id
]);
return redirect()->route('resume-builder.edit', $duplicatedResume)
->with('success', 'Resume duplicated successfully!');
} catch (\Exception $e) {
logger()->error('Resume duplication error', [
'error' => $e->getMessage(),
'user_id' => Auth::id(),
'resume_id' => $resume->id
]);
return back()->withErrors([
'error' => 'Unable to duplicate resume. Please try again.'
]);
}
}
/**
* Search user's resumes
*/
public function search(Request $request): \Illuminate\Http\JsonResponse
{
$query = $request->input('query', '');
$userId = Auth::id();
if (empty($query)) {
return response()->json([]);
}
$resumes = $this->resumeRepository
->getUserResumes($userId)
->filter(function ($resume) use ($query) {
return stripos($resume->title, $query) !== false ||
stripos($resume->description, $query) !== false;
})
->take(10)
->values();
return response()->json($resumes);
}
/**
* Get resume analytics
*/
public function analytics(Resume $resume): \Illuminate\Http\JsonResponse
{
$this->authorize('view', $resume);
$analytics = [
'view_count' => $resume->view_count,
'download_count' => $resume->download_count,
'completion_percentage' => $resume->completion_percentage,
'last_viewed_at' => $resume->last_viewed_at,
'last_downloaded_at' => $resume->last_downloaded_at,
'created_at' => $resume->created_at,
'updated_at' => $resume->updated_at,
];
return response()->json($analytics);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class TemplateController extends Controller
{
/**
* Display a listing of available templates.
*/
public function index(): View
{
// Get available templates
$templates = [
// You can replace this with actual template data from database
[
'id' => 1,
'name' => 'Professional',
'description' => 'Clean and professional template',
'preview_image' => '/images/templates/professional.jpg',
'is_premium' => false,
],
[
'id' => 2,
'name' => 'Modern',
'description' => 'Modern and stylish template',
'preview_image' => '/images/templates/modern.jpg',
'is_premium' => true,
],
[
'id' => 3,
'name' => 'Classic',
'description' => 'Traditional classic template',
'preview_image' => '/images/templates/classic.jpg',
'is_premium' => false,
],
];
return view('templates.index', compact('templates'));
}
/**
* Display the specified template.
*/
public function show(string $template): View
{
// Find template by ID or slug
// This is a placeholder - replace with actual template fetching logic
$templateData = [
'id' => 1,
'name' => ucfirst($template),
'description' => 'Template description',
'preview_image' => "/images/templates/{$template}.jpg",
'is_premium' => false,
];
return view('templates.show', compact('templateData'));
}
/**
* Preview the specified template.
*/
public function preview(string $template): View
{
// Generate preview for the template
// This would typically include sample data
$templateData = [
'id' => 1,
'name' => ucfirst($template),
'description' => 'Template description',
'preview_image' => "/images/templates/{$template}.jpg",
'is_premium' => false,
];
$sampleData = [
'name' => 'John Doe',
'title' => 'Software Developer',
'email' => 'john.doe@example.com',
'phone' => '+1 (555) 123-4567',
'summary' => 'Experienced software developer with expertise in web technologies.',
];
return view('templates.preview', compact('templateData', 'sampleData'));
}
}