From 0f39dbee62f6762ccd873ce202ac9e3449ab513a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 11 Mar 2026 23:32:54 -0400 Subject: [PATCH] fix endpoints --- .../Api/Scores/FinalController.php | 25 + .../Api/Scores/HomeworkController.php | 25 + .../Api/Scores/MidtermController.php | 25 + .../Api/Scores/ParticipationController.php | 25 + .../Api/Scores/ProjectController.php | 25 + .../Controllers/Api/Scores/QuizController.php | 25 + .../Api/Students/StudentController.php | 551 +++++++++ app/Models/EmergencyContact.php | 4 +- app/Services/Scores/ExamScoreService.php | 30 + app/Services/Scores/HomeworkScoreService.php | 41 + .../Scores/ParticipationScoreService.php | 39 + app/Services/Scores/ProjectScoreService.php | 41 + app/Services/Scores/QuizScoreService.php | 41 + .../Students/StudentDirectoryService.php | 41 +- bootstrap/app.php | 16 +- resources/docs/openapi.json | 1093 ++++++++++++++++- routes/api.php | 26 + 17 files changed, 2066 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/Scores/FinalController.php b/app/Http/Controllers/Api/Scores/FinalController.php index 8555dc4e..e364e5b7 100644 --- a/app/Http/Controllers/Api/Scores/FinalController.php +++ b/app/Http/Controllers/Api/Scores/FinalController.php @@ -68,6 +68,31 @@ class FinalController extends BaseApiController return response()->json(['ok' => true, 'updated' => $count]); } + public function bySchoolYear(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->service->listBySchoolYear('final_exam', $payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $data['schoolYear'], + 'count' => count($data['rows']), + 'rows' => $data['rows'], + ]); + } + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void { $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); diff --git a/app/Http/Controllers/Api/Scores/HomeworkController.php b/app/Http/Controllers/Api/Scores/HomeworkController.php index 6c2ead6d..dc2b9214 100644 --- a/app/Http/Controllers/Api/Scores/HomeworkController.php +++ b/app/Http/Controllers/Api/Scores/HomeworkController.php @@ -86,6 +86,31 @@ class HomeworkController extends BaseApiController return response()->json(['ok' => true, 'homework_index' => $nextIndex]); } + public function bySchoolYear(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->service->listBySchoolYear($payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $data['schoolYear'], + 'count' => count($data['rows']), + 'rows' => $data['rows'], + ]); + } + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void { $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); diff --git a/app/Http/Controllers/Api/Scores/MidtermController.php b/app/Http/Controllers/Api/Scores/MidtermController.php index cf8bcde6..64d84eed 100644 --- a/app/Http/Controllers/Api/Scores/MidtermController.php +++ b/app/Http/Controllers/Api/Scores/MidtermController.php @@ -68,6 +68,31 @@ class MidtermController extends BaseApiController return response()->json(['ok' => true, 'updated' => $count]); } + public function bySchoolYear(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->service->listBySchoolYear('midterm_exam', $payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $data['schoolYear'], + 'count' => count($data['rows']), + 'rows' => $data['rows'], + ]); + } + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void { $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); diff --git a/app/Http/Controllers/Api/Scores/ParticipationController.php b/app/Http/Controllers/Api/Scores/ParticipationController.php index 81e96a76..e73f8742 100644 --- a/app/Http/Controllers/Api/Scores/ParticipationController.php +++ b/app/Http/Controllers/Api/Scores/ParticipationController.php @@ -71,6 +71,31 @@ class ParticipationController extends BaseApiController return response()->json(['ok' => true, 'updated' => $count]); } + public function bySchoolYear(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->service->listBySchoolYear($payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $data['schoolYear'], + 'count' => count($data['rows']), + 'rows' => $data['rows'], + ]); + } + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void { $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); diff --git a/app/Http/Controllers/Api/Scores/ProjectController.php b/app/Http/Controllers/Api/Scores/ProjectController.php index 287f0b84..a5898497 100644 --- a/app/Http/Controllers/Api/Scores/ProjectController.php +++ b/app/Http/Controllers/Api/Scores/ProjectController.php @@ -86,6 +86,31 @@ class ProjectController extends BaseApiController return response()->json(['ok' => true, 'project_index' => $nextIndex]); } + public function bySchoolYear(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->service->listBySchoolYear($payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $data['schoolYear'], + 'count' => count($data['rows']), + 'rows' => $data['rows'], + ]); + } + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void { $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); diff --git a/app/Http/Controllers/Api/Scores/QuizController.php b/app/Http/Controllers/Api/Scores/QuizController.php index 3b764bae..33cba3c7 100644 --- a/app/Http/Controllers/Api/Scores/QuizController.php +++ b/app/Http/Controllers/Api/Scores/QuizController.php @@ -86,6 +86,31 @@ class QuizController extends BaseApiController return response()->json(['ok' => true, 'quiz_index' => $nextIndex]); } + public function bySchoolYear(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->service->listBySchoolYear($payload['school_year']); + + return response()->json([ + 'ok' => true, + 'school_year' => $data['schoolYear'], + 'count' => count($data['rows']), + 'rows' => $data['rows'], + ]); + } + private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void { $studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0)); diff --git a/app/Http/Controllers/Api/Students/StudentController.php b/app/Http/Controllers/Api/Students/StudentController.php index a1329e0c..f2a738a4 100644 --- a/app/Http/Controllers/Api/Students/StudentController.php +++ b/app/Http/Controllers/Api/Students/StudentController.php @@ -14,6 +14,14 @@ use App\Http\Resources\Students\StudentAssignmentResource; use App\Http\Resources\Students\StudentClassSectionResource; use App\Http\Resources\Students\StudentRemovedResource; use App\Http\Resources\Students\StudentScoreCardRowResource; +use App\Models\AttendanceRecord; +use App\Models\ClassSection; +use App\Models\EmergencyContact; +use App\Models\Incident; +use App\Models\SemesterScore; +use App\Models\Student; +use App\Models\StudentClass; +use App\Models\User; use App\Services\Students\StudentAssignmentService; use App\Services\Students\StudentAutoDistributionService; use App\Services\Students\StudentDirectoryService; @@ -21,6 +29,10 @@ use App\Services\Students\StudentProfileService; use App\Services\Students\StudentScoreCardService; use App\Services\Students\StudentStatusService; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Validator; class StudentController extends BaseApiController { @@ -50,6 +62,104 @@ class StudentController extends BaseApiController ]); } + public function index(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $data = $this->directoryService->listStudents($payload['school_year'] ?? null); + + return response()->json([ + 'ok' => true, + 'students' => $data['students'], + 'school_year' => $data['schoolYear'], + 'current_year' => $data['currentYear'], + ]); + } + + public function show(int $studentId): JsonResponse + { + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + return response()->json([ + 'ok' => true, + 'student' => $student->only([ + 'id', + 'school_id', + 'firstname', + 'lastname', + 'dob', + 'age', + 'gender', + 'is_active', + 'registration_grade', + 'is_new', + 'photo_consent', + 'parent_id', + 'registration_date', + 'tuition_paid', + 'rfid_tag', + 'semester', + 'year_of_registration', + 'school_year', + ]), + ]); + } + + public function store(Request $request): JsonResponse + { + $data = array_merge($request->query->all(), $request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'school_id' => ['nullable', 'string', 'max:50'], + 'firstname' => ['required', 'string', 'max:150'], + 'lastname' => ['required', 'string', 'max:150'], + 'dob' => ['nullable', 'date'], + 'age' => ['nullable', 'integer', 'min:0'], + 'gender' => ['nullable', 'string', 'max:20'], + 'is_active' => ['nullable', 'boolean'], + 'registration_grade' => ['nullable', 'string', 'max:50'], + 'is_new' => ['nullable', 'boolean'], + 'photo_consent' => ['nullable', 'boolean'], + 'parent_id' => ['nullable', 'integer', 'min:1'], + 'registration_date' => ['nullable', 'date'], + 'tuition_paid' => ['nullable', 'boolean'], + 'rfid_tag' => ['nullable', 'string', 'max:100'], + 'semester' => ['nullable', 'string', 'max:50'], + 'year_of_registration' => ['nullable', 'integer', 'min:1900'], + 'school_year' => ['required', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $student = Student::query()->create($payload); + + if (empty($student->school_id)) { + $student->school_id = $this->generateSchoolId($student->id); + $student->save(); + } + + return response()->json(['ok' => true, 'student' => $student], 201); + } + public function assignClass(StudentAssignClassRequest $request): JsonResponse { $payload = $request->validated(); @@ -140,6 +250,447 @@ class StudentController extends BaseApiController return response()->json($result + ['ok' => (bool) $result['ok']], $status); } + public function destroy(int $studentId): JsonResponse + { + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $student->update(['is_active' => 0]); + + return response()->json(['ok' => true]); + } + + public function classes(Request $request, int $studentId): JsonResponse + { + $validator = Validator::make($request->query->all(), [ + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $schoolYear = $validator->validated()['school_year'] ?? null; + + $rows = StudentClass::query() + ->select('student_class.*', 'cs.class_section_name') + ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'student_class.class_section_id') + ->where('student_class.student_id', $studentId) + ->when($schoolYear !== null && $schoolYear !== '', fn ($q) => $q->where('student_class.school_year', $schoolYear)) + ->orderBy('cs.class_section_name') + ->get() + ->toArray(); + + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'school_year' => $schoolYear, + 'classes' => $rows, + ]); + } + + public function assignClassForStudent(Request $request, int $studentId): JsonResponse + { + $data = array_merge($request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'student_id' => ['required', 'integer', 'min:1'], + 'class_section_id' => ['nullable', 'integer', 'min:1'], + 'class_section_ids' => ['nullable', 'array'], + 'class_section_ids.*' => ['integer', 'min:1'], + 'is_event_only' => ['nullable', 'boolean'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + if ((int) $payload['student_id'] !== $studentId) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['student_id' => ['student_id does not match the route studentId.']], + ], 422); + } + + $ids = $payload['class_section_ids'] ?? []; + if (empty($ids) && isset($payload['class_section_id'])) { + $ids = [(int) $payload['class_section_id']]; + } + + $result = $this->assignmentService->assignClasses( + $studentId, + $ids, + (bool) ($payload['is_event_only'] ?? false), + (int) (auth()->id() ?? 0) + ); + + $status = $result['ok'] ? 200 : 422; + + return response()->json($result + ['ok' => (bool) $result['ok']], $status); + } + + public function removeClassForStudent(int $studentId, int $classSectionId): JsonResponse + { + $result = $this->assignmentService->removeClass( + $studentId, + $classSectionId, + (int) (auth()->id() ?? 0) + ); + + $status = $result['ok'] ? 200 : 422; + + return response()->json($result + ['ok' => (bool) $result['ok']], $status); + } + + public function promote(Request $request, int $studentId): JsonResponse + { + $data = array_merge($request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'class_section_id' => ['required', 'integer', 'min:1'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $result = $this->assignmentService->assignClasses( + $studentId, + [(int) $payload['class_section_id']], + false, + (int) (auth()->id() ?? 0) + ); + + $status = $result['ok'] ? 200 : 422; + + return response()->json($result + ['ok' => (bool) $result['ok']], $status); + } + + public function attendance(Request $request, int $studentId): JsonResponse + { + $validator = Validator::make($request->query->all(), [ + 'school_year' => ['nullable', 'string', 'max:50'], + 'semester' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + + $rows = AttendanceRecord::query() + ->where('student_id', $studentId) + ->when(!empty($payload['school_year']), fn ($q) => $q->where('school_year', $payload['school_year'])) + ->when(!empty($payload['semester']), fn ($q) => $q->where('semester', $payload['semester'])) + ->orderBy('school_year', 'desc') + ->orderBy('semester', 'desc') + ->get() + ->toArray(); + + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'rows' => $rows, + ]); + } + + public function incidents(Request $request, int $studentId): JsonResponse + { + $validator = Validator::make($request->query->all(), [ + 'school_year' => ['nullable', 'string', 'max:50'], + 'semester' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + + $rows = Incident::query() + ->where('student_id', $studentId) + ->when(!empty($payload['school_year']), fn ($q) => $q->where('school_year', $payload['school_year'])) + ->when(!empty($payload['semester']), fn ($q) => $q->where('semester', $payload['semester'])) + ->orderByDesc('incident_datetime') + ->get() + ->toArray(); + + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'rows' => $rows, + ]); + } + + public function scores(Request $request, int $studentId): JsonResponse + { + $validator = Validator::make($request->query->all(), [ + 'school_year' => ['nullable', 'string', 'max:50'], + 'semester' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + + $rows = SemesterScore::query() + ->where('student_id', $studentId) + ->when(!empty($payload['school_year']), fn ($q) => $q->where('school_year', $payload['school_year'])) + ->when(!empty($payload['semester']), fn ($q) => $q->where('semester', $payload['semester'])) + ->orderBy('school_year', 'desc') + ->orderBy('semester', 'desc') + ->get() + ->toArray(); + + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'rows' => $rows, + ]); + } + + public function notes(int $studentId): JsonResponse + { + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'rows' => [], + ]); + } + + public function parents(int $studentId): JsonResponse + { + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $parent = null; + if (!empty($student->parent_id)) { + $parent = User::query() + ->select(['id', 'firstname', 'lastname', 'email', 'cellphone']) + ->find((int) $student->parent_id); + } + + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'parent' => $parent, + ]); + } + + public function emergencyContacts(int $studentId): JsonResponse + { + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $query = EmergencyContact::query(); + if (Schema::hasColumn('emergency_contacts', 'student_id')) { + $query->where('student_id', $studentId); + } + if (!empty($student->parent_id)) { + $query->orWhere('parent_id', $student->parent_id); + } + + $rows = $query + ->orderBy('emergency_contact_name') + ->get() + ->toArray(); + + return response()->json([ + 'ok' => true, + 'student_id' => $studentId, + 'rows' => $rows, + ]); + } + + public function addEmergencyContact(Request $request, int $studentId): JsonResponse + { + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $data = array_merge($request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'name' => ['required', 'string', 'max:150'], + 'cellphone' => ['required', 'string', 'max:30'], + 'email' => ['nullable', 'email', 'max:150'], + 'relation' => ['nullable', 'string', 'max:80'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $payload = $validator->validated(); + $create = [ + 'parent_id' => $student->parent_id, + 'emergency_contact_name' => $payload['name'], + 'cellphone' => $payload['cellphone'], + 'email' => $payload['email'] ?? null, + 'relation' => $payload['relation'] ?? null, + 'semester' => $payload['semester'] ?? null, + 'school_year' => $payload['school_year'] ?? null, + ]; + if (Schema::hasColumn('emergency_contacts', 'student_id')) { + $create['student_id'] = $studentId; + } + + $row = EmergencyContact::query()->create($create); + + return response()->json(['ok' => true, 'row' => $row], 201); + } + + public function updateEmergencyContact(Request $request, int $studentId, int $contactId): JsonResponse + { + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $data = array_merge($request->all(), $request->json()->all()); + $validator = Validator::make($data, [ + 'name' => ['nullable', 'string', 'max:150'], + 'cellphone' => ['nullable', 'string', 'max:30'], + 'email' => ['nullable', 'email', 'max:150'], + 'relation' => ['nullable', 'string', 'max:80'], + 'semester' => ['nullable', 'string', 'max:50'], + 'school_year' => ['nullable', 'string', 'max:50'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $contact = EmergencyContact::query()->find($contactId); + if (!$contact) { + return response()->json(['ok' => false, 'message' => 'Contact not found.'], 404); + } + + $payload = $validator->validated(); + $contact->update(array_filter([ + 'emergency_contact_name' => $payload['name'] ?? null, + 'cellphone' => $payload['cellphone'] ?? null, + 'email' => $payload['email'] ?? null, + 'relation' => $payload['relation'] ?? null, + 'semester' => $payload['semester'] ?? null, + 'school_year' => $payload['school_year'] ?? null, + ], static fn ($v) => $v !== null)); + + return response()->json(['ok' => true, 'row' => $contact]); + } + + public function updateRfid(Request $request, int $studentId): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'rfid_tag' => ['required', 'string', 'max:100'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $student->update(['rfid_tag' => $validator->validated()['rfid_tag']]); + + return response()->json(['ok' => true]); + } + + public function uploadPhoto(Request $request, int $studentId): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'photo' => ['required', 'file', 'image', 'max:5120'], + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $student = Student::query()->find($studentId); + if (!$student) { + return response()->json(['ok' => false, 'message' => 'Student not found.'], 404); + } + + $file = $request->file('photo'); + $ext = $file->getClientOriginalExtension() ?: 'jpg'; + $path = "student-photos/{$studentId}." . strtolower($ext); + Storage::disk('public')->putFileAs('student-photos', $file, "{$studentId}." . strtolower($ext)); + + return response()->json([ + 'ok' => true, + 'photo_url' => Storage::disk('public')->url($path), + ]); + } + + public function photo(int $studentId): JsonResponse + { + $base = "student-photos/{$studentId}"; + $candidates = ['jpg', 'jpeg', 'png', 'gif', 'webp']; + $disk = Storage::disk('public'); + + foreach ($candidates as $ext) { + $path = $base . '.' . $ext; + if ($disk->exists($path)) { + return response()->json([ + 'ok' => true, + 'photo_url' => $disk->url($path), + ]); + } + } + + return response()->json(['ok' => false, 'message' => 'Photo not found.'], 404); + } + + private function generateSchoolId(int $studentId): string + { + $year = (int) date('y'); + return 'STU' . $year . str_pad((string) $studentId, 4, '0', STR_PAD_LEFT); + } + public function scoreCard(int $studentId): JsonResponse { $result = $this->scoreCardService->scoreCard($studentId); diff --git a/app/Models/EmergencyContact.php b/app/Models/EmergencyContact.php index 198321ae..29ec8092 100755 --- a/app/Models/EmergencyContact.php +++ b/app/Models/EmergencyContact.php @@ -81,7 +81,7 @@ class EmergencyContact extends BaseModel { $row = static::query() ->where('parent_id', $parentId) - ->select(['emergency_contact_name', 'cellphone']) + ->select(['emergency_contact_name', 'cellphone', 'email']) ->first(); return $row ? $row->toArray() : null; @@ -93,4 +93,4 @@ class EmergencyContact extends BaseModel ->where('parent_id', $parentId) ->get(); } -} \ No newline at end of file +} diff --git a/app/Services/Scores/ExamScoreService.php b/app/Services/Scores/ExamScoreService.php index d5670317..d6addf87 100644 --- a/app/Services/Scores/ExamScoreService.php +++ b/app/Services/Scores/ExamScoreService.php @@ -131,4 +131,34 @@ class ExamScoreService return $count; } + + public function listBySchoolYear(string $table, ?string $schoolYear): array + { + $schoolYear = $this->term->schoolYear($schoolYear); + $schoolYearVariants = $this->term->schoolYearVariants($schoolYear); + if (empty($schoolYearVariants)) { + $schoolYearVariants = [$schoolYear]; + } + + $rows = DB::table($table . ' as ex') + ->select([ + 'ex.*', + 's.firstname as student_firstname', + 's.lastname as student_lastname', + 's.school_id as student_school_id', + ]) + ->leftJoin('students as s', 's.id', '=', 'ex.student_id') + ->whereIn('ex.school_year', $schoolYearVariants) + ->orderBy('ex.class_section_id') + ->orderBy('s.lastname') + ->orderBy('s.firstname') + ->get() + ->map(fn ($row) => (array) $row) + ->all(); + + return [ + 'rows' => $rows, + 'schoolYear' => $schoolYear, + ]; + } } diff --git a/app/Services/Scores/HomeworkScoreService.php b/app/Services/Scores/HomeworkScoreService.php index 827f24b8..2b5fb319 100644 --- a/app/Services/Scores/HomeworkScoreService.php +++ b/app/Services/Scores/HomeworkScoreService.php @@ -87,6 +87,47 @@ class HomeworkScoreService ]; } + public function listBySchoolYear(?string $schoolYear): array + { + $schoolYear = $this->term->schoolYear($schoolYear); + $schoolYearVariants = $this->term->schoolYearVariants($schoolYear); + if (empty($schoolYearVariants)) { + $schoolYearVariants = [$schoolYear]; + } + + $rows = Homework::query() + ->select([ + 'homework.id', + 'homework.student_id', + 'homework.school_id', + 'homework.class_section_id', + 'homework.updated_by', + 'homework.homework_index', + 'homework.score', + 'homework.comment', + 'homework.semester', + 'homework.school_year', + 'homework.created_at', + 'homework.updated_at', + 's.firstname as student_firstname', + 's.lastname as student_lastname', + ]) + ->leftJoin('students as s', 's.id', '=', 'homework.student_id') + ->whereIn('homework.school_year', $schoolYearVariants) + ->orderBy('homework.class_section_id') + ->orderBy('s.lastname') + ->orderBy('s.firstname') + ->orderBy('homework.homework_index') + ->get() + ->map(fn ($row) => $row->toArray()) + ->all(); + + return [ + 'rows' => $rows, + 'schoolYear' => $schoolYear, + ]; + } + private function isLockedForAnySchoolYear(int $classSectionId, string $semester, array $schoolYearVariants): bool { foreach ($schoolYearVariants as $year) { diff --git a/app/Services/Scores/ParticipationScoreService.php b/app/Services/Scores/ParticipationScoreService.php index dadd9e73..f8620add 100644 --- a/app/Services/Scores/ParticipationScoreService.php +++ b/app/Services/Scores/ParticipationScoreService.php @@ -71,6 +71,45 @@ class ParticipationScoreService ]; } + public function listBySchoolYear(?string $schoolYear): array + { + $schoolYear = $this->term->schoolYear($schoolYear); + $schoolYearVariants = $this->term->schoolYearVariants($schoolYear); + if (empty($schoolYearVariants)) { + $schoolYearVariants = [$schoolYear]; + } + + $rows = Participation::query() + ->select([ + 'participation.id', + 'participation.student_id', + 'participation.school_id', + 'participation.class_section_id', + 'participation.updated_by', + 'participation.score', + 'participation.comment', + 'participation.semester', + 'participation.school_year', + 'participation.created_at', + 'participation.updated_at', + 's.firstname as student_firstname', + 's.lastname as student_lastname', + ]) + ->leftJoin('students as s', 's.id', '=', 'participation.student_id') + ->whereIn('participation.school_year', $schoolYearVariants) + ->orderBy('participation.class_section_id') + ->orderBy('s.lastname') + ->orderBy('s.firstname') + ->get() + ->map(fn ($row) => $row->toArray()) + ->all(); + + return [ + 'rows' => $rows, + 'schoolYear' => $schoolYear, + ]; + } + public function update(int $classSectionId, string $semester, string $schoolYear, array $scores, array $missingOk, int $updatedBy): int { if (GradingLock::isLocked($classSectionId, $semester, $schoolYear)) { diff --git a/app/Services/Scores/ProjectScoreService.php b/app/Services/Scores/ProjectScoreService.php index 10e6bffe..4221f775 100644 --- a/app/Services/Scores/ProjectScoreService.php +++ b/app/Services/Scores/ProjectScoreService.php @@ -83,6 +83,47 @@ class ProjectScoreService ]; } + public function listBySchoolYear(?string $schoolYear): array + { + $schoolYear = $this->term->schoolYear($schoolYear); + $schoolYearVariants = $this->term->schoolYearVariants($schoolYear); + if (empty($schoolYearVariants)) { + $schoolYearVariants = [$schoolYear]; + } + + $rows = Project::query() + ->select([ + 'project.id', + 'project.student_id', + 'project.school_id', + 'project.class_section_id', + 'project.updated_by', + 'project.project_index', + 'project.score', + 'project.comment', + 'project.semester', + 'project.school_year', + 'project.created_at', + 'project.updated_at', + 's.firstname as student_firstname', + 's.lastname as student_lastname', + ]) + ->leftJoin('students as s', 's.id', '=', 'project.student_id') + ->whereIn('project.school_year', $schoolYearVariants) + ->orderBy('project.class_section_id') + ->orderBy('s.lastname') + ->orderBy('s.firstname') + ->orderBy('project.project_index') + ->get() + ->map(fn ($row) => $row->toArray()) + ->all(); + + return [ + 'rows' => $rows, + 'schoolYear' => $schoolYear, + ]; + } + public function update(int $classSectionId, string $semester, string $schoolYear, array $scores, array $missingOk, int $updatedBy): int { if (GradingLock::isLocked($classSectionId, $semester, $schoolYear)) { diff --git a/app/Services/Scores/QuizScoreService.php b/app/Services/Scores/QuizScoreService.php index 0f567094..d5d1a7dc 100644 --- a/app/Services/Scores/QuizScoreService.php +++ b/app/Services/Scores/QuizScoreService.php @@ -82,6 +82,47 @@ class QuizScoreService ]; } + public function listBySchoolYear(?string $schoolYear): array + { + $schoolYear = $this->term->schoolYear($schoolYear); + $schoolYearVariants = $this->term->schoolYearVariants($schoolYear); + if (empty($schoolYearVariants)) { + $schoolYearVariants = [$schoolYear]; + } + + $rows = Quiz::query() + ->select([ + 'quiz.id', + 'quiz.student_id', + 'quiz.school_id', + 'quiz.class_section_id', + 'quiz.updated_by', + 'quiz.quiz_index', + 'quiz.score', + 'quiz.comment', + 'quiz.semester', + 'quiz.school_year', + 'quiz.created_at', + 'quiz.updated_at', + 's.firstname as student_firstname', + 's.lastname as student_lastname', + ]) + ->leftJoin('students as s', 's.id', '=', 'quiz.student_id') + ->whereIn('quiz.school_year', $schoolYearVariants) + ->orderBy('quiz.class_section_id') + ->orderBy('s.lastname') + ->orderBy('s.firstname') + ->orderBy('quiz.quiz_index') + ->get() + ->map(fn ($row) => $row->toArray()) + ->all(); + + return [ + 'rows' => $rows, + 'schoolYear' => $schoolYear, + ]; + } + public function update(int $classSectionId, string $semester, string $schoolYear, array $scores, array $missingOk, int $updatedBy): int { if (GradingLock::isLocked($classSectionId, $semester, $schoolYear)) { diff --git a/app/Services/Students/StudentDirectoryService.php b/app/Services/Students/StudentDirectoryService.php index 883ef6c5..62f130a7 100644 --- a/app/Services/Students/StudentDirectoryService.php +++ b/app/Services/Students/StudentDirectoryService.php @@ -66,7 +66,7 @@ class StudentDirectoryService 'student_id' => $studentId, 'name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')), 'age' => $student['age'] ?? 'N/A', - 'email' => $emergencyInfo['emergency_contact_name'] ?? '', + 'email' => $emergencyInfo['email'] ?? '', 'phone' => $emergencyInfo['cellphone'] ?? '', 'registration_grade' => $student['registration_grade'] ?? 'N/A', 'class_section_name' => $sectionDisplay !== '' ? $sectionDisplay : 'No class assigned', @@ -161,4 +161,43 @@ class StudentDirectoryService 'school_year' => $schoolYear, ]; } + + public function listStudents(?string $schoolYear = null): array + { + $context = $this->configService->context(); + $selectedYear = $schoolYear ?: (string) ($context['school_year'] ?? ''); + + $students = Student::query() + ->select([ + 'id', + 'school_id', + 'firstname', + 'lastname', + 'dob', + 'age', + 'gender', + 'is_active', + 'registration_grade', + 'is_new', + 'photo_consent', + 'parent_id', + 'registration_date', + 'tuition_paid', + 'rfid_tag', + 'semester', + 'year_of_registration', + 'school_year', + ]) + ->when($selectedYear !== '', fn ($q) => $q->where('school_year', $selectedYear)) + ->orderBy('lastname') + ->orderBy('firstname') + ->get() + ->toArray(); + + return [ + 'students' => $students, + 'schoolYear' => $selectedYear, + 'currentYear' => (string) ($context['school_year'] ?? ''), + ]; + } } diff --git a/bootstrap/app.php b/bootstrap/app.php index f609b86c..f916d222 100755 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,6 +3,8 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; +use Illuminate\Auth\AuthenticationException; +use Illuminate\Http\Request; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( @@ -22,7 +24,19 @@ return Application::configure(basePath: dirname(__DIR__)) 'cleanup.scheduler' => \App\Http\Middleware\CleanupScheduler::class, 'auth.docs' => \App\Http\Middleware\ApiDocsAuth::class, ]); + + $middleware->redirectGuestsTo(function (Request $request) { + if ($request->is('api/*') || $request->expectsJson()) { + return null; + } + + return route('login'); + }); }) ->withExceptions(function (Exceptions $exceptions): void { - // + $exceptions->render(function (AuthenticationException $e, Request $request) { + if ($request->is('api/*') || $request->expectsJson()) { + return response()->json(['message' => 'Unauthorized.'], 401); + } + }); })->create(); diff --git a/resources/docs/openapi.json b/resources/docs/openapi.json index eb3889fa..3217b3b5 100755 --- a/resources/docs/openapi.json +++ b/resources/docs/openapi.json @@ -1588,11 +1588,23 @@ "schema": { "type": "object", "properties": { + "school_id": {"type": "string"}, + "school_year": {"type": "string"}, "firstname": {"type": "string"}, "lastname": {"type": "string"}, "dob": {"type": "string", "format": "date"}, - "gender": {"type": "string", "enum": ["male", "female"]}, - "parent_id": {"type": "integer"} + "age": {"type": "integer"}, + "gender": {"type": "string"}, + "is_active": {"type": "boolean"}, + "registration_grade": {"type": "string"}, + "is_new": {"type": "boolean"}, + "photo_consent": {"type": "boolean"}, + "parent_id": {"type": "integer"}, + "registration_date": {"type": "string", "format": "date"}, + "tuition_paid": {"type": "boolean"}, + "rfid_tag": {"type": "string"}, + "semester": {"type": "string"}, + "year_of_registration": {"type": "integer"} } } } @@ -4049,7 +4061,25 @@ "application/json": { "schema": { "type": "object", - "additionalProperties": true + "properties": { + "school_id": {"type": "string"}, + "school_year": {"type": "string"}, + "firstname": {"type": "string"}, + "lastname": {"type": "string"}, + "dob": {"type": "string", "format": "date"}, + "age": {"type": "integer"}, + "gender": {"type": "string"}, + "is_active": {"type": "boolean"}, + "registration_grade": {"type": "string"}, + "is_new": {"type": "boolean"}, + "photo_consent": {"type": "boolean"}, + "parent_id": {"type": "integer"}, + "registration_date": {"type": "string", "format": "date"}, + "tuition_paid": {"type": "boolean"}, + "rfid_tag": {"type": "string"}, + "semester": {"type": "string"}, + "year_of_registration": {"type": "integer"} + } } } } @@ -4107,6 +4137,1063 @@ } } }, + "/api/v1/scores/comments": { + "get": { + "operationId": "getScoreComments", + "tags": ["Scores"], + "summary": "ScoreCommentController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "class_section_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "semester", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/projects": { + "get": { + "operationId": "getProjectScores", + "tags": ["Scores"], + "summary": "ProjectController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "class_section_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "semester", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/quizzes": { + "get": { + "operationId": "getQuizScores", + "tags": ["Scores"], + "summary": "QuizController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "class_section_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "semester", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/midterm": { + "get": { + "operationId": "getMidtermScores", + "tags": ["Scores"], + "summary": "MidtermController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "class_section_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "semester", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/final": { + "get": { + "operationId": "getFinalScores", + "tags": ["Scores"], + "summary": "FinalController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "class_section_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "semester", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/participation": { + "get": { + "operationId": "getParticipationScores", + "tags": ["Scores"], + "summary": "ParticipationController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "class_section_id", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "minimum": 1 + } + }, + { + "name": "semester", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/projects/by-year": { + "get": { + "operationId": "getProjectScoresByYear", + "tags": ["Scores"], + "summary": "ProjectController::bySchoolYear", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/quizzes/by-year": { + "get": { + "operationId": "getQuizScoresByYear", + "tags": ["Scores"], + "summary": "QuizController::bySchoolYear", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/homework/by-year": { + "get": { + "operationId": "getHomeworkScoresByYear", + "tags": ["Scores"], + "summary": "HomeworkController::bySchoolYear", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/midterm/by-year": { + "get": { + "operationId": "getMidtermScoresByYear", + "tags": ["Scores"], + "summary": "MidtermController::bySchoolYear", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/final/by-year": { + "get": { + "operationId": "getFinalScoresByYear", + "tags": ["Scores"], + "summary": "FinalController::bySchoolYear", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/scores/participation/by-year": { + "get": { + "operationId": "getParticipationScoresByYear", + "tags": ["Scores"], + "summary": "ParticipationController::bySchoolYear", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/students": { + "get": { + "operationId": "listStudentsByYear", + "tags": ["Students"], + "summary": "StudentController::index", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "school_year", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "post": { + "operationId": "createStudent", + "tags": ["Students"], + "summary": "StudentController::store", + "security": [{"bearerAuth": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["school_year", "firstname", "lastname"], + "properties": { + "school_year": {"type": "string"}, + "firstname": {"type": "string"}, + "lastname": {"type": "string"}, + "dob": {"type": "string", "format": "date"}, + "age": {"type": "integer"}, + "gender": {"type": "string"}, + "is_active": {"type": "boolean"}, + "registration_grade": {"type": "string"}, + "is_new": {"type": "boolean"}, + "photo_consent": {"type": "boolean"}, + "parent_id": {"type": "integer"}, + "registration_date": {"type": "string", "format": "date"}, + "tuition_paid": {"type": "boolean"}, + "rfid_tag": {"type": "string"}, + "semester": {"type": "string"}, + "year_of_registration": {"type": "integer"} + } + } + } + } + }, + "responses": { + "201": {"description": "Created"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/api/v1/students/{studentId}": { + "get": { + "operationId": "getStudent", + "tags": ["Students"], + "summary": "StudentController::show", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "studentId", + "in": "path", + "required": true, + "schema": {"type": "integer"} + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "404": {"description": "Not found"} + } + }, + "patch": { + "operationId": "updateStudent", + "tags": ["Students"], + "summary": "StudentController::update", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "studentId", + "in": "path", + "required": true, + "schema": {"type": "integer"} + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": { + "description": "Validation error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ErrorResponse"} + } + } + } + } + }, + "delete": { + "operationId": "deactivateStudent", + "tags": ["Students"], + "summary": "StudentController::destroy", + "security": [{"bearerAuth": []}], + "parameters": [ + { + "name": "studentId", + "in": "path", + "required": true, + "schema": {"type": "integer"} + } + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "404": {"description": "Not found"} + } + } + }, + "/api/v1/students/{studentId}/classes": { + "get": { + "operationId": "getStudentClasses", + "tags": ["Students"], + "summary": "StudentController::classes", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}}, + {"name": "school_year", "in": "query", "schema": {"type": "string"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + }, + "post": { + "operationId": "assignStudentClasses", + "tags": ["Students"], + "summary": "StudentController::assignClassForStudent", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["student_id", "class_section_id"], + "properties": { + "student_id": {"type": "integer"}, + "class_section_id": {"type": "integer"}, + "class_section_ids": {"type": "array", "items": {"type": "integer"}}, + "is_event_only": {"type": "boolean"} + } + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/classes/{classSectionId}": { + "delete": { + "operationId": "removeStudentClass", + "tags": ["Students"], + "summary": "StudentController::removeClassForStudent", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}}, + {"name": "classSectionId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/promote": { + "post": { + "operationId": "promoteStudent", + "tags": ["Students"], + "summary": "StudentController::promote", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["name", "cellphone"], + "properties": { + "name": {"type": "string"}, + "cellphone": {"type": "string"}, + "email": {"type": "string", "format": "email"}, + "relation": {"type": "string"}, + "semester": {"type": "string"}, + "school_year": {"type": "string"} + } + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/attendance": { + "get": { + "operationId": "getStudentAttendance", + "tags": ["Students"], + "summary": "StudentController::attendance", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}}, + {"name": "school_year", "in": "query", "schema": {"type": "string"}}, + {"name": "semester", "in": "query", "schema": {"type": "string"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + } + }, + "/api/v1/students/{studentId}/incidents": { + "get": { + "operationId": "getStudentIncidents", + "tags": ["Students"], + "summary": "StudentController::incidents", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}}, + {"name": "school_year", "in": "query", "schema": {"type": "string"}}, + {"name": "semester", "in": "query", "schema": {"type": "string"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + } + }, + "/api/v1/students/{studentId}/scores": { + "get": { + "operationId": "getStudentScores", + "tags": ["Students"], + "summary": "StudentController::scores", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}}, + {"name": "school_year", "in": "query", "schema": {"type": "string"}}, + {"name": "semester", "in": "query", "schema": {"type": "string"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + } + }, + "/api/v1/students/{studentId}/notes": { + "get": { + "operationId": "getStudentNotes", + "tags": ["Students"], + "summary": "StudentController::notes", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + } + }, + "/api/v1/students/{studentId}/parents": { + "get": { + "operationId": "getStudentParents", + "tags": ["Students"], + "summary": "StudentController::parents", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "404": {"description": "Not found"} + } + } + }, + "/api/v1/students/{studentId}/emergency-contacts": { + "get": { + "operationId": "getStudentEmergencyContacts", + "tags": ["Students"], + "summary": "StudentController::emergencyContacts", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + }, + "post": { + "operationId": "addStudentEmergencyContact", + "tags": ["Students"], + "summary": "StudentController::addEmergencyContact", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": {"type": "object", "additionalProperties": true} + } + } + }, + "responses": { + "201": {"description": "Created"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/emergency-contacts/{contactId}": { + "patch": { + "operationId": "updateStudentEmergencyContact", + "tags": ["Students"], + "summary": "StudentController::updateEmergencyContact", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}}, + {"name": "contactId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": {"type": "object", "additionalProperties": true} + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/rfid": { + "post": { + "operationId": "updateStudentRfid", + "tags": ["Students"], + "summary": "StudentController::updateRfid", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": {"type": "object", "additionalProperties": true} + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/photo": { + "get": { + "operationId": "getStudentPhoto", + "tags": ["Students"], + "summary": "StudentController::photo", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "404": {"description": "Not found"} + } + }, + "post": { + "operationId": "uploadStudentPhoto", + "tags": ["Students"], + "summary": "StudentController::uploadPhoto", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "photo": {"type": "string", "format": "binary"} + } + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/report-card": { + "get": { + "operationId": "getStudentReportCard", + "tags": ["Students"], + "summary": "ReportCardsController::studentReport", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/{studentId}/score-card": { + "get": { + "operationId": "getStudentScoreCard", + "tags": ["Students"], + "summary": "StudentController::scoreCard", + "security": [{"bearerAuth": []}], + "parameters": [ + {"name": "studentId", "in": "path", "required": true, "schema": {"type": "integer"}} + ], + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"} + } + } + }, + "/api/v1/students/assign-class": { + "post": { + "operationId": "assignClass", + "tags": ["Students"], + "summary": "StudentController::assignClass", + "security": [{"bearerAuth": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["student_id", "class_section_id"], + "properties": { + "student_id": {"type": "integer"}, + "class_section_id": {"type": "array", "items": {"type": "integer"}}, + "is_event_only": {"type": "boolean"} + } + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/remove-class": { + "post": { + "operationId": "removeClass", + "tags": ["Students"], + "summary": "StudentController::removeClass", + "security": [{"bearerAuth": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["student_id", "class_section_id"], + "properties": { + "student_id": {"type": "integer"}, + "class_section_id": {"type": "integer"} + } + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, + "/api/v1/students/set-active": { + "post": { + "operationId": "setActive", + "tags": ["Students"], + "summary": "StudentController::setActive", + "security": [{"bearerAuth": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["student_id", "is_active"], + "properties": { + "student_id": {"type": "integer"}, + "is_active": {"type": "boolean"} + } + } + } + } + }, + "responses": { + "200": {"description": "OK"}, + "401": {"$ref": "#/components/responses/UnauthorizedError"}, + "422": {"description": "Validation error"} + } + } + }, "/api/v1/time": { "get": { "operationId": "getServerTime", diff --git a/routes/api.php b/routes/api.php index d1255e9d..d284e2cd 100755 --- a/routes/api.php +++ b/routes/api.php @@ -390,6 +390,8 @@ Route::prefix('v1')->group(function () { }); Route::prefix('students')->group(function () { + Route::get('/', [StudentApiController::class, 'index']); + Route::post('/', [StudentApiController::class, 'store']); Route::get('assignments', [StudentApiController::class, 'assignments']); Route::post('assign-class', [StudentApiController::class, 'assignClass']); Route::post('remove-class', [StudentApiController::class, 'removeClass']); @@ -398,6 +400,24 @@ Route::prefix('v1')->group(function () { Route::post('auto-distribute', [StudentApiController::class, 'autoDistribute']); Route::get('promotion-totals', [StudentApiController::class, 'promotionTotals']); Route::patch('{studentId}', [StudentApiController::class, 'update']); + Route::delete('{studentId}', [StudentApiController::class, 'destroy']); + Route::get('{studentId}', [StudentApiController::class, 'show']); + Route::get('{studentId}/classes', [StudentApiController::class, 'classes']); + Route::post('{studentId}/classes', [StudentApiController::class, 'assignClassForStudent']); + Route::delete('{studentId}/classes/{classSectionId}', [StudentApiController::class, 'removeClassForStudent']); + Route::post('{studentId}/promote', [StudentApiController::class, 'promote']); + Route::get('{studentId}/attendance', [StudentApiController::class, 'attendance']); + Route::get('{studentId}/incidents', [StudentApiController::class, 'incidents']); + Route::get('{studentId}/scores', [StudentApiController::class, 'scores']); + Route::get('{studentId}/notes', [StudentApiController::class, 'notes']); + Route::get('{studentId}/parents', [StudentApiController::class, 'parents']); + Route::get('{studentId}/emergency-contacts', [StudentApiController::class, 'emergencyContacts']); + Route::post('{studentId}/emergency-contacts', [StudentApiController::class, 'addEmergencyContact']); + Route::patch('{studentId}/emergency-contacts/{contactId}', [StudentApiController::class, 'updateEmergencyContact']); + Route::post('{studentId}/rfid', [StudentApiController::class, 'updateRfid']); + Route::post('{studentId}/photo', [StudentApiController::class, 'uploadPhoto']); + Route::get('{studentId}/photo', [StudentApiController::class, 'photo']); + Route::get('{studentId}/report-card', [ReportCardsController::class, 'studentReport']); Route::get('{studentId}/score-card', [StudentApiController::class, 'scoreCard']); }); @@ -720,34 +740,40 @@ Route::prefix('v1')->group(function () { Route::prefix('projects')->group(function () { Route::get('/', [ProjectScoreController::class, 'index']); + Route::get('by-year', [ProjectScoreController::class, 'bySchoolYear']); Route::post('/', [ProjectScoreController::class, 'update']); Route::post('columns', [ProjectScoreController::class, 'addColumn']); }); Route::prefix('quizzes')->group(function () { Route::get('/', [QuizScoreController::class, 'index']); + Route::get('by-year', [QuizScoreController::class, 'bySchoolYear']); Route::post('/', [QuizScoreController::class, 'update']); Route::post('columns', [QuizScoreController::class, 'addColumn']); }); Route::prefix('homework')->group(function () { Route::get('/', [HomeworkScoreController::class, 'index']); + Route::get('by-year', [HomeworkScoreController::class, 'bySchoolYear']); Route::post('/', [HomeworkScoreController::class, 'update']); Route::post('columns', [HomeworkScoreController::class, 'addColumn']); }); Route::prefix('midterm')->group(function () { Route::get('/', [MidtermScoreController::class, 'index']); + Route::get('by-year', [MidtermScoreController::class, 'bySchoolYear']); Route::post('/', [MidtermScoreController::class, 'update']); }); Route::prefix('final')->group(function () { Route::get('/', [FinalScoreController::class, 'index']); + Route::get('by-year', [FinalScoreController::class, 'bySchoolYear']); Route::post('/', [FinalScoreController::class, 'update']); }); Route::prefix('participation')->group(function () { Route::get('/', [ParticipationScoreController::class, 'index']); + Route::get('by-year', [ParticipationScoreController::class, 'bySchoolYear']); Route::post('/', [ParticipationScoreController::class, 'update']); });