Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions job-board/app/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,7 +26,7 @@ class Handler extends ExceptionHandler
public function register(): void
{
$this->reportable(function (Throwable $e) {
//

});
}
}
}
29 changes: 29 additions & 0 deletions job-board/app/Http/Controllers/JobApplicationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Http\Controllers;

use App\Models\Job;
use Illuminate\Http\Request;

class JobApplicationController extends Controller
{
public function create(Job $job)
{
$this->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.');
}
}
2 changes: 1 addition & 1 deletion job-board/app/Http/Controllers/JobController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')]
);
}

Expand Down
15 changes: 15 additions & 0 deletions job-board/app/Models/Job.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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();
}
}
24 changes: 24 additions & 0 deletions job-board/app/Models/JobApplication.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class JobApplication extends Model
{
use HasFactory;

protected $fillable = ['user_id', 'expected_salary'];

public function job(): BelongsTo
{
return $this->belongsTo(Job::class);
}

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
6 changes: 6 additions & 0 deletions job-board/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,4 +48,9 @@ public function employer(): HasOne
{
return $this->hasOne(Employer::class);
}

public function jobApplications(): HasMany
{
return $this->hasMany(JobApplication::class);
}
}
72 changes: 72 additions & 0 deletions job-board/app/Policies/JobPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Policies;

use App\Models\Job;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Access\Response;

class JobPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}

/**
* Determine whether the user can view the model.
*/
public function view(User $user, Job $job): bool
{
return true;
}

/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}

/**
* Determine whether the user can update the model.
*/
public function update(User $user, Job $job): bool
{
return false;
}

/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Job $job): bool
{
return false;
}

/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Job $job): bool
{
return false;
}

/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Job $job): bool
{
return false;
}

public function apply(User $user, Job $job): bool
{
return !$job->hasUserApplied($user);
}
}
4 changes: 2 additions & 2 deletions job-board/app/Providers/AuthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class AuthServiceProvider extends ServiceProvider
* @var array<class-string, class-string>
*/
protected $policies = [
//

];

/**
Expand All @@ -23,4 +23,4 @@ public function boot(): void
{
//
}
}
}
23 changes: 23 additions & 0 deletions job-board/database/factories/JobApplicationFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\JobApplication>
*/
class JobApplicationFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'expected_salary' => $this->faker->numberBetween(4000, 170000),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use App\Models\Job;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('job_applications', function (Blueprint $table) {
$table->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');
}
};
10 changes: 9 additions & 1 deletion job-board/database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]);
}
}
}
}
22 changes: 21 additions & 1 deletion job-board/resources/views/components/layout.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ class="from-10% via-30% to-90% mx-auto mt-10 max-w-2xl bg-gradient-to-r from-ind
</li>
</ul>

<ul class="flex space-x-2">
<ul class="flex items-center space-x-2">
@auth
<li>
{{ auth()->user()->name ?? 'Anynomus' }}
</li>
<li>
Applications
</li>
<li>
<form action="{{ route('auth.destroy') }}" method="POST">
@csrf
Expand All @@ -37,6 +40,23 @@ class="from-10% via-30% to-90% mx-auto mt-10 max-w-2xl bg-gradient-to-r from-ind
@endauth
</ul>
</nav>
@if (session('error'))
<div
class="my-8 rounded-md border-l-4 border-red-300 bg-red-100 p-4 text-red-700 opacity-75"
role="alert">
<p class="font-bold">Error!</p>
<p>{{ session('error') }}</p>
</div>
@endif
@if (session('success'))
<div
class="my-8 rounded-md border-l-4 border-green-300 bg-green-100 p-4 text-green-700 opacity-75"
role="alert">
<p class="font-bold">Success!</p>
<p>{{ session('success') }}</p>
</div>
@endif

{{ $slot }}
</body>

Expand Down
12 changes: 12 additions & 0 deletions job-board/resources/views/job/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
<p class="mb-4 text-sm text-slate-500">
{!! nl2br(e($job->description)) !!}
</p>

@can('apply', $job)
<div class="flex items-center space-x-2">
<x-link-button :href="route('job.application.create', $job)">
Apply
</x-link-button>
<div class="text-sm text-slate-500">{{ $job->job_applications_count }} other
{{ Str::plural('applicant', $job->job_applications_count) }}</div>
</div>
@else
<div class="text-center text-sm font-medium text-green-500">You already applied to this job</div>
@endcan
</x-job-card>

<x-card class="mb-4">
Expand Down
26 changes: 26 additions & 0 deletions job-board/resources/views/job_application/create.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<x-layout>
<x-breadcrumbs class="mb-4"
:links="[
'Jobs' => route('jobs.index'),
$job->title => route('jobs.show', $job),
'Apply' => '#',
]" />

<x-job-card :$job></x-job-card>

<x-card>
<h2 class="mb-4 text-lg font-medium">Your Job Application</h2>

<form action="{{ route('job.application.store', $job) }}" method="POST">
@csrf
<div class="mb-4">
<label for="expected_salary" class="mb-2 block text-sm font-medium text-slate-900">
Expected Salary
</label>
<x-text-input type="number" name="expected_salary" />
</div>

<x-button class="w-full">Apply</x-button>
</form>
</x-card>
</x-layout>
Loading