diff --git a/job-board/app/Exceptions/Handler.php b/job-board/app/Exceptions/Handler.php index 56af264..570fac7 100644 --- a/job-board/app/Exceptions/Handler.php +++ b/job-board/app/Exceptions/Handler.php @@ -2,7 +2,9 @@ namespace App\Exceptions; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Http\Request; use Throwable; class Handler extends ExceptionHandler @@ -24,7 +26,7 @@ class Handler extends ExceptionHandler public function register(): void { $this->reportable(function (Throwable $e) { - // + }); } -} +} \ No newline at end of file diff --git a/job-board/app/Http/Controllers/JobApplicationController.php b/job-board/app/Http/Controllers/JobApplicationController.php new file mode 100644 index 0000000..39cd790 --- /dev/null +++ b/job-board/app/Http/Controllers/JobApplicationController.php @@ -0,0 +1,29 @@ +authorize('apply', $job); + return view('job_application.create', ['job' => $job]); + } + + public function store(Job $job, Request $request) + { + $this->authorize('apply', $job); + $job->jobApplications()->create([ + ...$request->validate([ + 'expected_salary' => 'required|min:1|max:1000000' + ]), + 'user_id' => auth()->user()->id + ]); + + return redirect()->route('jobs.show', $job) + ->with('success', 'Job application submitted.'); + } +} \ No newline at end of file diff --git a/job-board/app/Http/Controllers/JobController.php b/job-board/app/Http/Controllers/JobController.php index 7e398c1..ff6d5f2 100644 --- a/job-board/app/Http/Controllers/JobController.php +++ b/job-board/app/Http/Controllers/JobController.php @@ -49,7 +49,7 @@ public function show(Job $job) { return view( 'job.show', - ['job' => $job->load('employer.jobs')] + ['job' => $job->load('employer.jobs')->loadCount('jobApplications')] ); } diff --git a/job-board/app/Models/Job.php b/job-board/app/Models/Job.php index 64e6290..c2e743e 100644 --- a/job-board/app/Models/Job.php +++ b/job-board/app/Models/Job.php @@ -2,10 +2,12 @@ namespace App\Models; +use Illuminate\Auth\Authenticatable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Query\Builder as QueryBuilder; class Job extends Model @@ -25,6 +27,11 @@ public function employer(): BelongsTo return $this->belongsTo(Employer::class); } + public function jobApplications(): HasMany + { + return $this->hasMany(JobApplication::class); + } + public function scopeFilter(Builder|QueryBuilder $query, array $filters): Builder|QueryBuilder { return $query->when($filters['search'] ?? null, function ($query, $search) { @@ -45,4 +52,12 @@ public function scopeFilter(Builder|QueryBuilder $query, array $filters): Builde $query->where('category', $category); }); } + + public function hasUserApplied(Authenticatable|User|int $user): bool + { + return $this->where('id', $this->id)->whereHas( + 'jobApplications', + fn($query) => $query->where('user_id', '=', $user->id ?? $user) + )->exists(); + } } \ No newline at end of file diff --git a/job-board/app/Models/JobApplication.php b/job-board/app/Models/JobApplication.php new file mode 100644 index 0000000..9609ea2 --- /dev/null +++ b/job-board/app/Models/JobApplication.php @@ -0,0 +1,24 @@ +belongsTo(Job::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} \ No newline at end of file diff --git a/job-board/app/Models/User.php b/job-board/app/Models/User.php index ad6f3a7..810680f 100644 --- a/job-board/app/Models/User.php +++ b/job-board/app/Models/User.php @@ -4,6 +4,7 @@ // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -47,4 +48,9 @@ public function employer(): HasOne { return $this->hasOne(Employer::class); } + + public function jobApplications(): HasMany + { + return $this->hasMany(JobApplication::class); + } } \ No newline at end of file diff --git a/job-board/app/Policies/JobPolicy.php b/job-board/app/Policies/JobPolicy.php new file mode 100644 index 0000000..2c4d00e --- /dev/null +++ b/job-board/app/Policies/JobPolicy.php @@ -0,0 +1,72 @@ +hasUserApplied($user); + } +} \ No newline at end of file diff --git a/job-board/app/Providers/AuthServiceProvider.php b/job-board/app/Providers/AuthServiceProvider.php index 54756cd..b9aeac1 100644 --- a/job-board/app/Providers/AuthServiceProvider.php +++ b/job-board/app/Providers/AuthServiceProvider.php @@ -13,7 +13,7 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - // + ]; /** @@ -23,4 +23,4 @@ public function boot(): void { // } -} +} \ No newline at end of file diff --git a/job-board/database/factories/JobApplicationFactory.php b/job-board/database/factories/JobApplicationFactory.php new file mode 100644 index 0000000..2b8a486 --- /dev/null +++ b/job-board/database/factories/JobApplicationFactory.php @@ -0,0 +1,23 @@ + + */ +class JobApplicationFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'expected_salary' => $this->faker->numberBetween(4000, 170000), + ]; + } +} \ No newline at end of file diff --git a/job-board/database/migrations/2023_05_29_111115_create_job_applications_table.php b/job-board/database/migrations/2023_05_29_111115_create_job_applications_table.php new file mode 100644 index 0000000..c0ad931 --- /dev/null +++ b/job-board/database/migrations/2023_05_29_111115_create_job_applications_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignIdFor(User::class); + $table->foreignIdFor(Job::class); + + $table->unsignedInteger('expected_salary'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('job_applications'); + } +}; \ No newline at end of file diff --git a/job-board/database/seeders/DatabaseSeeder.php b/job-board/database/seeders/DatabaseSeeder.php index 7d3746f..d72fca8 100644 --- a/job-board/database/seeders/DatabaseSeeder.php +++ b/job-board/database/seeders/DatabaseSeeder.php @@ -34,6 +34,14 @@ public function run(): void ]); } - // \App\Models\User::factory(10)->create(); + foreach ($users as $user) { + $jobs = \App\Models\Job::inRandomOrder()->take(rand(0, 4))->get(); + foreach ($jobs as $job) { + \App\Models\JobApplication::factory()->create([ + 'job_id' => $job->id, + 'user_id' => $user->id, + ]); + } + } } } \ No newline at end of file diff --git a/job-board/resources/views/components/layout.blade.php b/job-board/resources/views/components/layout.blade.php index caaeaf8..374330b 100644 --- a/job-board/resources/views/components/layout.blade.php +++ b/job-board/resources/views/components/layout.blade.php @@ -18,11 +18,14 @@ class="from-10% via-30% to-90% mx-auto mt-10 max-w-2xl bg-gradient-to-r from-ind -