diff --git a/backend/app/Http/Controllers/InternshipController.php b/backend/app/Http/Controllers/InternshipController.php index 1eeb796..b5dea8d 100644 --- a/backend/app/Http/Controllers/InternshipController.php +++ b/backend/app/Http/Controllers/InternshipController.php @@ -37,7 +37,7 @@ class InternshipController extends Controller }); $internships->each(function ($internship) { - $internship->status = InternshipStatus::whereColumn('internship_id', '=', $internship->id)->orderByDesc('changed')->get()->first()->makeHidden(['created_at', 'updated_at', 'id']); + $internship->status = InternshipStatus::whereInternshipId($internship->id)->orderByDesc('changed')->get()->first()->makeHidden(['created_at', 'updated_at', 'id']); $internship->status->modified_by = User::find($internship->status->modified_by)->makeHidden(['created_at', 'updated_at', 'email_verified_at']); }); @@ -46,12 +46,36 @@ class InternshipController extends Controller $internship->end = Carbon::parse($internship->end)->format('d.m.Y'); }); + $internships->each(function ($internship) { + $internship->agreement = $internship->agreement !== null; + $internship->report = $internship->report !== null; + }); + return response()->json($internships); } - public function all_student() + public function all_my() { - $internships = Internship::where('user_id', auth()->id())->get()->makeHidden(['created_at', 'updated_at']); + $user = auth()->user(); + + if ($user->role === 'STUDENT') { + $internships = Internship::whereUserId($user->id)->get()->makeHidden(['created_at', 'updated_at']); + } elseif ($user->role === 'EMPLOYER') { + $company = Company::whereContact($user->id)->first(); + if (!$company) { + return response()->json(['message' => 'No company associated with this user.'], 404); + } + $internships = Internship::whereCompanyId($company->id)->get()->makeHidden(['created_at', 'updated_at']); + } else { + abort(403, 'Unauthorized'); + } + + if($user->role === "EMPLOYER") { + $internships->each(function ($internship) { + $internship->user = User::find($internship->user_id)->makeHidden(['created_at', 'updated_at', 'email_verified_at']); + unset($internship->user_id); + }); + } $internships->each(function ($internship) { $internship->company = Company::find($internship->company_id)->makeHidden(['created_at', 'updated_at']); @@ -73,6 +97,11 @@ class InternshipController extends Controller $internship->end = Carbon::parse($internship->end)->format('d.m.Y'); }); + $internships->each(function ($internship) { + $internship->agreement = $internship->agreement !== null; + $internship->report = $internship->report !== null; + }); + return response()->json($internships); } @@ -87,22 +116,75 @@ class InternshipController extends Controller ], 400); } - if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id) { - abort(403, 'Unauthorized'); - } - $internship->company = Company::find($internship->company_id)->makeHidden(['created_at', 'updated_at']); unset($internship->company_id); + if($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->company->contact) { + abort(403, 'Unauthorized'); + } + $internship->contact = User::find($internship->company->contact)->makeHidden(['created_at', 'updated_at', 'email_verified_at']); unset($internship->company->contact); - - $internship->status = InternshipStatus::whereColumn('internship_id', '=', $internship->id)->orderByDesc('changed')->get()->first()->makeHidden(['created_at', 'updated_at', 'id']); + + $internship->status = InternshipStatus::whereInternshipId($internship->id)->orderByDesc('changed')->get()->first()->makeHidden(['created_at', 'updated_at', 'id']); $internship->status->modified_by = User::find($internship->status->modified_by)->makeHidden(['created_at', 'updated_at', 'email_verified_at']); + $internship->agreement = $internship->agreement !== null; + $internship->report = $internship->report !== null; + return response()->json($internship); } + public function get_agreement(int $id) { + $user = auth()->user(); + $internship = Internship::find($id); + + if(!$internship) { + return response()->json([ + 'message' => 'No such internship exists.' + ], 400); + } + + if(!$internship->agreement) { + return response()->json([ + 'message' => 'No agreement file exists for this internship.' + ], 404); + } + + if($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->company->contact) { + abort(403, 'Unauthorized'); + } + + return response($internship->agreement, 200) + ->header('Content-Type', 'application/pdf') + ->header('Content-Disposition', 'attachment; filename="agreement_' . $id . '.pdf"'); + } + + public function get_report(int $id) { + $user = auth()->user(); + $internship = Internship::find($id); + + if(!$internship) { + return response()->json([ + 'message' => 'No such internship exists.' + ], 400); + } + + if(!$internship->report) { + return response()->json([ + 'message' => 'No report file exists for this internship.' + ], 404); + } + + if($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->company->contact) { + abort(403, 'Unauthorized'); + } + + return response($internship->report, 200) + ->header('Content-Type', 'application/pdf') + ->header('Content-Disposition', 'attachment; filename="report_' . $id . '.pdf"'); + } + /** * Display a listing of the resource. */ @@ -178,7 +260,7 @@ class InternshipController extends Controller ], 400); } - if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id) { + if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->company->contact) { abort(403, 'Unauthorized'); } @@ -189,6 +271,48 @@ class InternshipController extends Controller return response()->noContent(); } + public function update_documents(int $id, Request $request) { + $user = auth()->user(); + $internship = Internship::find($id); + + if(!$internship) { + return response()->json([ + 'message' => 'No such internship exists.' + ], 400); + } + + if ($internship->user_id !== $user->id && $user->id !== $internship->company->contact) { + abort(403, 'Unauthorized'); + } + + $request->validate([ + 'agreement' => ['nullable', 'file', 'mimes:pdf', 'max:10240'], + 'report' => ['nullable', 'file', 'mimes:pdf', 'max:10240'], + 'report_confirmed' => ['required', 'boolean'], + ]); + + if ($request->hasFile('agreement')) { + $internship->agreement = file_get_contents($request->file('agreement')->getRealPath()); + } + + if ($request->hasFile('report')) { + $internship->report = file_get_contents($request->file('report')->getRealPath()); + } + + if($user->role === 'EMPLOYER') { + if($request->report_confirmed && (!$internship->agreement || !$internship->report)) { + return response()->json([ + 'message' => 'Report cannot be confirmed without an agreement and report.' + ], 400); + } + + $internship->report_confirmed = $request->report_confirmed; + } + + $internship->save(); + return response()->noContent(); + } + /** * Update the specified resource in storage. */ diff --git a/backend/app/Http/Controllers/InternshipStatusController.php b/backend/app/Http/Controllers/InternshipStatusController.php index 3cee9dc..2621faa 100644 --- a/backend/app/Http/Controllers/InternshipStatusController.php +++ b/backend/app/Http/Controllers/InternshipStatusController.php @@ -19,8 +19,8 @@ class InternshipStatusController extends Controller ], 400); } - $internship = Internship::where($id); - if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id) { + $internship = Internship::find($id); + if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->company->contact) { abort(403, 'Unauthorized'); } @@ -41,7 +41,7 @@ class InternshipStatusController extends Controller ], 400); } - if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->contact) { + if ($user->role !== 'ADMIN' && $internship->user_id !== $user->id && $user->id !== $internship->company->contact) { abort(403, 'Unauthorized'); } @@ -105,9 +105,7 @@ class InternshipStatusController extends Controller ], 400); } - $company_contact = User::find($internship->contact); - - if ($user->role !== 'ADMIN' && $user->id !== $company_contact->id) { + if ($user->role !== 'ADMIN' && $user->id !== $internship->company->contact) { abort(403, 'Unauthorized'); } @@ -139,11 +137,10 @@ class InternshipStatusController extends Controller } private function possibleNewStatuses(string $current_status, string $userRole) { + if($userRole === "STUDENT") return []; + switch ($current_status) { case 'SUBMITTED': - if ($userRole === 'EMPLOYER') { - return []; - } return ['CONFIRMED', 'DENIED']; case 'CONFIRMED': if ($userRole === 'EMPLOYER') { diff --git a/backend/app/Models/Internship.php b/backend/app/Models/Internship.php index a549885..4974156 100644 --- a/backend/app/Models/Internship.php +++ b/backend/app/Models/Internship.php @@ -24,5 +24,24 @@ class Internship extends Model 'semester', 'position_description', 'agreement', + 'report', + 'report_confirmed', ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'report_confirmed' => 'boolean', + ]; + } + + public function company() + { + return $this->belongsTo(Company::class, 'company_id'); + } } diff --git a/backend/database/factories/InternshipFactory.php b/backend/database/factories/InternshipFactory.php index 33837de..4b0e117 100644 --- a/backend/database/factories/InternshipFactory.php +++ b/backend/database/factories/InternshipFactory.php @@ -28,6 +28,8 @@ class InternshipFactory extends Factory 'semester' => fake()->randomElement(["WINTER", "SUMMER"]), 'position_description' => fake()->jobTitle(), 'agreement' => null, + 'report' => null, + 'report_confirmed' => false, ]; } } diff --git a/backend/database/migrations/2025_10_20_193012_create_internships_table.php b/backend/database/migrations/2025_10_20_193012_create_internships_table.php index 1b3f750..83571d4 100644 --- a/backend/database/migrations/2025_10_20_193012_create_internships_table.php +++ b/backend/database/migrations/2025_10_20_193012_create_internships_table.php @@ -21,6 +21,8 @@ return new class extends Migration $table->enum("semester", ["WINTER", "SUMMER"])->nullable(false); $table->string("position_description")->nullable(false); $table->binary("agreement")->nullable(true); + $table->binary("report")->nullable(true); + $table->boolean("report_confirmed")->nullable(false)->default(false); $table->timestamps(); }); } diff --git a/backend/routes/api.php b/backend/routes/api.php index 8eef125..35bae9d 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -34,7 +34,7 @@ Route::post('/password-reset', [RegisteredUserController::class, 'reset_password Route::prefix('/internships')->group(function () { Route::get("/", [InternshipController::class, 'all'])->name("api.internships"); - Route::get("/my", [InternshipController::class, 'all_student'])->name("api.internships.student"); + Route::get("/my", [InternshipController::class, 'all_my'])->name("api.internships.my"); Route::middleware("auth:sanctum")->group(function () { Route::prefix('/{id}')->group(function () { @@ -42,6 +42,9 @@ Route::prefix('/internships')->group(function () { Route::put("/status", [InternshipStatusController::class, 'update'])->name("api.internships.status.update"); Route::get("/statuses", [InternshipStatusController::class, 'get'])->name("api.internships.get"); Route::get("/next-statuses", [InternshipStatusController::class, 'get_next_states'])->name("api.internships.status.next.get"); + Route::get("/agreement", [InternshipController::class, 'get_agreement'])->name("api.internships.agreement.get"); + Route::get("/report", [InternshipController::class, 'get_report'])->name("api.internships.report.get"); + Route::post("/documents", [InternshipController::class, 'update_documents'])->name("api.internships.documents.set"); Route::post("/basic", [InternshipController::class, 'update_basic'])->name("api.internships.update.basic"); }); diff --git a/frontend/app/components/InternshipDocumentEditor.vue b/frontend/app/components/InternshipDocumentEditor.vue new file mode 100644 index 0000000..903a9ce --- /dev/null +++ b/frontend/app/components/InternshipDocumentEditor.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/frontend/app/components/InternshipDocumentViewer.vue b/frontend/app/components/InternshipDocumentViewer.vue new file mode 100644 index 0000000..a517b78 --- /dev/null +++ b/frontend/app/components/InternshipDocumentViewer.vue @@ -0,0 +1,85 @@ + + + diff --git a/frontend/app/components/InternshipEditor.vue b/frontend/app/components/InternshipEditor.vue index add890c..2cdccbb 100644 --- a/frontend/app/components/InternshipEditor.vue +++ b/frontend/app/components/InternshipEditor.vue @@ -77,7 +77,7 @@ function triggerSubmit() { start: dateTimeFixup(form.value.start as any), end: dateTimeFixup(form.value.end as any), year_of_study: form.value.year_of_study, - semester: form.value.semester === "Zimný" ? "WINTER" : "SUMMER", + semester: form.value.semester, position_description: form.value.description }; diff --git a/frontend/app/pages/dashboard/admin/internships/edit/[id].vue b/frontend/app/pages/dashboard/admin/internships/edit/[id].vue index 11a5f93..df93096 100644 --- a/frontend/app/pages/dashboard/admin/internships/edit/[id].vue +++ b/frontend/app/pages/dashboard/admin/internships/edit/[id].vue @@ -94,8 +94,7 @@ const { data, error, refresh } = await useSanctumFetch(`/api/interns

Dokumenty

-

...

-
+ diff --git a/frontend/app/pages/dashboard/company.vue b/frontend/app/pages/dashboard/company.vue deleted file mode 100644 index 6edc630..0000000 --- a/frontend/app/pages/dashboard/company.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/app/pages/dashboard/company/index.vue b/frontend/app/pages/dashboard/company/index.vue new file mode 100644 index 0000000..ee77806 --- /dev/null +++ b/frontend/app/pages/dashboard/company/index.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/pages/dashboard/company/internships/edit/[id].vue b/frontend/app/pages/dashboard/company/internships/edit/[id].vue new file mode 100644 index 0000000..74cac5d --- /dev/null +++ b/frontend/app/pages/dashboard/company/internships/edit/[id].vue @@ -0,0 +1,116 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/pages/dashboard/company/internships/index.vue b/frontend/app/pages/dashboard/company/internships/index.vue new file mode 100644 index 0000000..7ff86fd --- /dev/null +++ b/frontend/app/pages/dashboard/company/internships/index.vue @@ -0,0 +1,87 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/pages/dashboard/student/internship/edit/[id].vue b/frontend/app/pages/dashboard/student/internship/edit/[id].vue index dacf34a..f330165 100644 --- a/frontend/app/pages/dashboard/student/internship/edit/[id].vue +++ b/frontend/app/pages/dashboard/student/internship/edit/[id].vue @@ -1,5 +1,7 @@ @@ -27,5 +100,11 @@ const user = useSanctumUser(); #page-container-card { padding-left: 10px; padding-right: 10px; + padding-bottom: 10px; +} + +hr { + margin-top: 20px; + margin-bottom: 20px; } \ No newline at end of file diff --git a/frontend/app/types/internships.ts b/frontend/app/types/internships.ts index 49b3805..96e285a 100644 --- a/frontend/app/types/internships.ts +++ b/frontend/app/types/internships.ts @@ -12,7 +12,9 @@ export interface Internship { year_of_study: number; semester: string; position_description: string; - agreement?: Uint8Array; + agreement: boolean; + report: boolean; + report_confirmed: boolean; status: InternshipStatusData; }; @@ -24,5 +26,4 @@ export interface NewInternship { year_of_study: number; semester: string; position_description: string; - agreement?: Uint8Array; }; \ No newline at end of file