// Файл: app/Http/Controllers/AuthController.php all(), [ 'email' => 'required|email|unique:users', 'password' => 'required|min:8|confirmed', 'first_name' => 'required|string|max:100', 'last_name' => 'required|string|max:100', 'date_of_birth' => 'required|date', 'school' => 'required|string|max:255', 'country' => 'required|string|max:100', 'city' => 'required|string|max:100', 'student_id_image' => 'required|image|mimes:jpeg,png,jpg|max:2048' ]); if ($validator->fails()) { return response()->json([ 'errors' => $validator->errors() ], 422); } $imagePath = null; if ($request->hasFile('student_id_image')) { $imagePath = $request->file('student_id_image')->store('student_ids', 'public'); } $user = User::create([ 'email' => $request->email, 'password' => Hash::make($request->password), 'first_name' => $request->first_name, 'last_name' => $request->last_name, 'date_of_birth' => $request->date_of_birth, 'school' => $request->school, 'country' => $request->country, 'city' => $request->city, 'student_id_image' => $imagePath, 'role' => 'student' ]); $token = $user->createToken('zhanna_education')->plainTextToken; return response()->json([ 'user' => $user, 'token' => $token ], 201); } public function login(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|email', 'password' => 'required' ]); if ($validator->fails()) { return response()->json([ 'errors' => $validator->errors() ], 422); } if (Auth::attempt($request->only('email', 'password'))) { $user = Auth::user(); if (!$user->is_active) { return response()->json([ 'message' => 'Account deactivated' ], 403); } $token = $user->createToken('zhanna_education')->plainTextToken; return response()->json([ 'user' => $user, 'token' => $token ]); } return response()->json([ 'message' => 'Invalid credentials' ], 401); } public function logout(Request $request) { $request->user()->currentAccessToken()->delete(); return response()->json([ 'message' => 'Logged out successfully' ]); } public function user(Request $request) { return response()->json($request->user()); } } // Файл: app/Http/Controllers/Controller.php only(['level', 'subject', 'region', 'school']); $query = Leaderboard::with('user'); foreach ($filters as $key => $value) { if ($value) { $query->where($key, 'like', "%{$value}%"); } } $leaderboard = $query->orderBy('total_score', 'desc') ->orderBy('time_spent', 'asc') ->limit(100) ->get(); return response()->json($leaderboard); } public function publicIndex(Request $request) { $filters = $request->only(['level', 'subject', 'region', 'school']); $query = Leaderboard::with(['user' => function($q) { $q->select('id', 'first_name', 'last_name'); }]); foreach ($filters as $key => $value) { if ($value) { $query->where($key, 'like', "%{$value}%"); } } $leaderboard = $query->orderBy('total_score', 'desc') ->orderBy('time_spent', 'asc') ->limit(50) ->get() ->map(function($item) { return [ 'rank' => $item->rank, 'user_name' => $item->user->first_name . ' ' . $item->user->last_name, 'school' => $item->school, 'region' => $item->region, 'total_score' => $item->total_score, 'time_spent' => $item->time_spent ]; }); return response()->json($leaderboard); } } // Файл: app/Http/Controllers/QuestionController.php paginate(20); return response()->json($questions); } public function store(Request $request) { $validated = $request->validate([ 'test_id' => 'required|exists:tests,id', 'question_text' => 'required|string', 'options' => 'required|array|min:2', 'correct_answer' => 'required|integer|min:0', 'points' => 'sometimes|integer|min:1', 'explanation' => 'sometimes|string', 'image_url' => 'sometimes|url', 'time_estimate' => 'sometimes|integer|min:10', 'difficulty' => 'sometimes|in:easy,medium,hard' ]); $question = Question::create($validated); return response()->json([ 'message' => 'Question created successfully', 'question' => $question ], 201); } public function show(Question $question) { return response()->json($question->load('test')); } public function update(Request $request, Question $question) { $validated = $request->validate([ 'question_text' => 'sometimes|required|string', 'options' => 'sometimes|required|array|min:2', 'correct_answer' => 'sometimes|required|integer|min:0', 'points' => 'sometimes|integer|min:1', 'explanation' => 'sometimes|string', 'image_url' => 'sometimes|url', 'time_estimate' => 'sometimes|integer|min:10', 'difficulty' => 'sometimes|in:easy,medium,hard' ]); $question->update($validated); return response()->json([ 'message' => 'Question updated successfully', 'question' => $question ]); } public function destroy(Question $question) { $question->delete(); return response()->json([ 'message' => 'Question deleted successfully' ]); } } // Файл: app/Http/Controllers/ResearchPaperController.php user()->id)->get(); return response()->json($papers); } public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|string|max:255', 'abstract' => 'required|string', 'paper_file' => 'required|file|mimes:pdf|max:10240' ]); $filePath = $request->file('paper_file')->store('research-papers', 'public'); $paper = ResearchPaper::create([ 'user_id' => $request->user()->id, 'title' => $validated['title'], 'abstract' => $validated['abstract'], 'paper_file' => $filePath ]); return response()->json([ 'message' => 'Research paper submitted successfully', 'paper' => $paper ], 201); } public function show(ResearchPaper $researchPaper) { $this->authorize('view', $researchPaper); return response()->json($researchPaper); } public function update(Request $request, ResearchPaper $researchPaper) { $this->authorize('update', $researchPaper); $validated = $request->validate([ 'title' => 'sometimes|required|string|max:255', 'abstract' => 'sometimes|required|string', ]); $researchPaper->update($validated); return response()->json([ 'message' => 'Research paper updated successfully', 'paper' => $researchPaper ]); } public function destroy(ResearchPaper $researchPaper) { $this->authorize('delete', $researchPaper); $researchPaper->delete(); return response()->json([ 'message' => 'Research paper deleted successfully' ]); } } // Файл: app/Http/Controllers/SubjectController.php get(); return response()->json($subjects); } public function publicIndex() { $subjects = Subject::where('is_active', true)->get(); return response()->json($subjects); } public function register(Request $request, Subject $subject) { $user = $request->user(); // Check if already registered $existingRegistration = SubjectRegistration::where('user_id', $user->id) ->where('subject_id', $subject->id) ->first(); if ($existingRegistration) { return response()->json([ 'message' => 'Already registered for this subject' ], 400); } // Create registration $registration = SubjectRegistration::create([ 'user_id' => $user->id, 'subject_id' => $subject->id, 'competition_level_id' => 1, // Start from level 1 'payment_status' => 'pending' ]); return response()->json([ 'message' => 'Successfully registered for subject', 'registration' => $registration ]); } } // Файл: app/Http/Controllers/TestController.php query('level'); $subject = $request->query('subject'); $query = Test::with(['subject', 'competitionLevel']); if ($level) { $query->where('competition_level_id', $level); } if ($subject) { $query->where('subject_id', $subject); } $tests = $query->where('is_active', true)->get(); return response()->json([ 'tests' => $tests, 'subjects' => Subject::where('is_active', true)->get(), 'upcoming' => Test::where('scheduled_start', '>', now()) ->where('is_active', true) ->orderBy('scheduled_start', 'asc') ->limit(5) ->get() ]); } public function show(Test $test) { $test->load(['questions', 'subject', 'competitionLevel']); return response()->json($test); } } // Файл: app/Http/Controllers/TestSessionController.php isActive()) { return response()->json([ 'message' => 'Test is not available' ], 403); } // Проверка, зарегистрирован ли пользователь на предмет if (!$request->user()->subjectRegistrations() ->where('subject_id', $test->subject_id) ->where('competition_level_id', $test->competition_level_id) ->where('payment_status', 'completed') ->exists()) { return response()->json([ 'message' => 'Not registered for this subject' ], 403); } // Проверка, не начат ли уже тест $existingSession = TestSession::where('user_id', $request->user()->id) ->where('test_id', $test->id) ->whereIn('status', ['in_progress', 'completed']) ->first(); if ($existingSession) { return response()->json([ 'message' => 'Test already attempted', 'session' => $existingSession ], 409); } $session = TestSession::create([ 'user_id' => $request->user()->id, 'test_id' => $test->id, 'start_time' => now(), 'status' => 'in_progress', 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent() ]); return response()->json([ 'session' => $session, 'test' => $test->load('questions') ]); } public function submitAnswer(Request $request, TestSession $session) { if ($session->status !== 'in_progress') { return response()->json([ 'message' => 'Test session is not active' ], 403); } $validated = $request->validate([ 'question_id' => 'required|exists:questions,id', 'selected_option' => 'required|integer', 'time_spent' => 'required|integer' ]); $question = Question::find($validated['question_id']); $isCorrect = $validated['selected_option'] === $question->correct_answer; $userAnswer = UserAnswer::updateOrCreate( [ 'test_session_id' => $session->id, 'question_id' => $validated['question_id'] ], [ 'selected_option' => $validated['selected_option'], 'is_correct' => $isCorrect, 'time_spent' => $validated['time_spent'] ] ); return response()->json([ 'answer' => $userAnswer, 'is_correct' => $isCorrect ]); } public function finish(Request $request, TestSession $session) { if ($session->status !== 'in_progress') { return response()->json([ 'message' => 'Test session is not active' ], 403); } DB::transaction(function () use ($session) { $session->update([ 'end_time' => now(), 'time_spent' => now()->diffInSeconds($session->start_time), 'total_score' => $session->calculateScore(), 'status' => 'completed' ]); // Проверка квалификации на следующий уровень $this->checkQualification($session); }); return response()->json([ 'session' => $session->fresh(), 'message' => 'Test completed successfully' ]); } public function results(TestSession $session) { $session->load(['userAnswers.question', 'test']); return response()->json([ 'session' => $session, 'results' => [ 'total_score' => $session->total_score, 'time_spent' => $session->time_spent, 'correct_answers' => $session->userAnswers()->where('is_correct', true)->count(), 'total_questions' => $session->userAnswers()->count() ] ]); } private function checkQualification(TestSession $session) { $test = $session->test; $threshold = QualificationThreshold::where('competition_level_id', $test->competition_level_id) ->where('subject_id', $test->subject_id) ->first(); if (!$threshold) { return; } $totalSessions = TestSession::where('test_id', $test->id) ->where('status', 'completed') ->count(); if ($threshold->threshold_type === 'percentage') { $betterSessions = TestSession::where('test_id', $test->id) ->where('status', 'completed') ->where('total_score', '>', $session->total_score) ->count(); $percentile = (($totalSessions - $betterSessions) / $totalSessions) * 100; if ($percentile <= $threshold->threshold_value) { QualifiedUser::create([ 'user_id' => $session->user_id, 'competition_level_id' => $test->competition_level_id + 1, 'subject_id' => $test->subject_id, 'qualified_at' => now() ]); } } else { // Логика для fixed_number $topSessions = TestSession::where('test_id', $test->id) ->where('status', 'completed') ->orderBy('total_score', 'desc') ->orderBy('time_spent', 'asc') ->limit($threshold->threshold_value) ->pluck('id') ->toArray(); if (in_array($session->id, $topSessions)) { QualifiedUser::create([ 'user_id' => $session->user_id, 'competition_level_id' => $test->competition_level_id + 1, 'subject_id' => $test->subject_id, 'qualified_at' => now() ]); } } } } // Файл: app/Http/Controllers/UserController.php user()->load(['subjectRegistrations.subject', 'testSessions.test']); return response()->json($user); } public function update(Request $request) { $user = $request->user(); $validated = $request->validate([ 'first_name' => 'sometimes|required|string|max:100', 'last_name' => 'sometimes|required|string|max:100', 'email' => 'sometimes|required|email|unique:users,email,' . $user->id, 'school' => 'sometimes|required|string|max:255', 'country' => 'sometimes|required|string|max:100', 'city' => 'sometimes|required|string|max:100', 'password' => 'sometimes|required|min:8|confirmed' ]); if (isset($validated['password'])) { $validated['password'] = Hash::make($validated['password']); } $user->update($validated); return response()->json([ 'message' => 'Profile updated successfully', 'user' => $user ]); } } // Файл: app/Http/Controllers/VideoSessionController.php user()->id) ->orWhereHas('researchPaper', function($q) use ($request) { $q->where('user_id', $request->user()->id); }) ->with(['researchPaper.user', 'judge']) ->get(); return response()->json($sessions); } public function store(Request $request) { $validated = $request->validate([ 'research_paper_id' => 'required|exists:research_papers,id', 'scheduled_time' => 'required|date', 'duration' => 'required|integer|min:15|max:120' ]); $session = VideoSession::create([ 'research_paper_id' => $validated['research_paper_id'], 'judge_id' => $request->user()->id, 'scheduled_time' => $validated['scheduled_time'], 'duration' => $validated['duration'], 'meeting_url' => $this->generateMeetingUrl() ]); return response()->json([ 'message' => 'Video session scheduled successfully', 'session' => $session ], 201); } public function show(VideoSession $videoSession) { $this->authorize('view', $videoSession); return response()->json($videoSession->load(['researchPaper.user', 'judge'])); } public function update(Request $request, VideoSession $videoSession) { $this->authorize('update', $videoSession); $validated = $request->validate([ 'scheduled_time' => 'sometimes|required|date', 'duration' => 'sometimes|required|integer|min:15|max:120' ]); $videoSession->update($validated); return response()->json([ 'message' => 'Video session updated successfully', 'session' => $videoSession ]); } public function destroy(VideoSession $videoSession) { $this->authorize('delete', $videoSession); $videoSession->delete(); return response()->json([ 'message' => 'Video session deleted successfully' ]); } public function join(VideoSession $videoSession) { $this->authorize('join', $videoSession); return response()->json([ 'meeting_url' => $videoSession->meeting_url ]); } private function generateMeetingUrl() { // This would typically integrate with a video conferencing API return 'https://meet.zhanna-education.com/' . uniqid(); } } // Файл: app/Models/CompetitionLevel.php 'boolean', 'level' => 'integer', 'minimum_score' => 'integer', 'qualification_threshold' => 'integer' ]; public function tests() { return $this->hasMany(Test::class); } public function qualificationThresholds() { return $this->hasMany(QualificationThreshold::class); } public function qualifiedUsers() { return $this->hasMany(QualifiedUser::class); } public function scopeActive($query) { return $query->where('is_active', true); } public function getNextLevelAttribute() { return self::where('level', $this->level + 1) ->active() ->first(); } } // Файл: app/Models/QualificationThreshold.php 'datetime' ]; } // Файл: app/Models/Subject.php 'boolean' ]; public function tests() { return $this->hasMany(Test::class); } public function subjectRegistrations() { return $this->hasMany(SubjectRegistration::class); } public function qualificationThresholds() { return $this->hasMany(QualificationThreshold::class); } public function scopeActive($query) { return $query->where('is_active', true); } } // Файл: app/Models/SubjectRegistration.php 'datetime' ]; public function user() { return $this->belongsTo(User::class); } public function subject() { return $this->belongsTo(Subject::class); } public function competitionLevel() { return $this->belongsTo(CompetitionLevel::class); } public function isPaid() { return $this->payment_status === 'completed'; } public function scopePaid($query) { return $query->where('payment_status', 'completed'); } public function scopePending($query) { return $query->where('payment_status', 'pending'); } } // Файл: app/Models/Test.php 'datetime', 'scheduled_end' => 'datetime', 'is_active' => 'boolean', 'options' => 'array', 'time_limit' => 'integer', 'total_questions' => 'integer', 'total_score' => 'integer', 'passing_score' => 'integer' ]; public function subject() { return $this->belongsTo(Subject::class); } public function competitionLevel() { return $this->belongsTo(CompetitionLevel::class); } public function questions() { return $this->hasMany(Question::class); } public function testSessions() { return $this->hasMany(TestSession::class); } public function isActive() { return $this->is_active && ($this->scheduled_start === null || now()->gte($this->scheduled_start)) && ($this->scheduled_end === null || now()->lte($this->scheduled_end)); } public function isUpcoming() { return $this->scheduled_start && now()->lt($this->scheduled_start); } public function isCompleted() { return $this->scheduled_end && now()->gt($this->scheduled_end); } public function getTimeRemainingAttribute() { if (!$this->scheduled_end) { return null; } return now()->diffInSeconds($this->scheduled_end, false); } public function getStatusAttribute() { if ($this->isUpcoming()) { return 'upcoming'; } if ($this->isActive()) { return 'active'; } if ($this->isCompleted()) { return 'completed'; } return 'inactive'; } public function scopeActive($query) { return $query->where('is_active', true) ->where(function($q) { $q->whereNull('scheduled_start') ->orWhere('scheduled_start', '<=', now()); }) ->where(function($q) { $q->whereNull('scheduled_end') ->orWhere('scheduled_end', '>=', now()); }); } public function scopeUpcoming($query) { return $query->where('is_active', true) ->where('scheduled_start', '>', now()) ->orderBy('scheduled_start', 'asc'); } public function getAverageScoreAttribute() { $completedSessions = $this->testSessions() ->where('status', 'completed') ->count(); if ($completedSessions === 0) { return 0; } return $this->testSessions() ->where('status', 'completed') ->avg('total_score'); } public function getCompletionRateAttribute() { $totalSessions = $this->testSessions()->count(); $completedSessions = $this->testSessions() ->where('status', 'completed') ->count(); if ($totalSessions === 0) { return 0; } return ($completedSessions / $totalSessions) * 100; } } // Файл: app/Models/TestSession.php 'datetime', 'end_time' => 'datetime', 'options' => 'array' ]; public function user() { return $this->belongsTo(User::class); } public function test() { return $this->belongsTo(Test::class); } public function userAnswers() { return $this->hasMany(UserAnswer::class); } public function calculateScore() { $correctAnswers = $this->userAnswers()->where('is_correct', true)->count(); $timeBonus = max(0, ($this->test->time_limit * 60 - $this->time_spent) / 10); return $correctAnswers + $timeBonus; } public function isActive() { return $this->status === 'in_progress' && now()->lt($this->start_time->addMinutes($this->test->time_limit)); } public function timeRemaining() { if (!$this->isActive()) { return 0; } $endTime = $this->start_time->addMinutes($this->test->time_limit); return now()->diffInSeconds($endTime, false); } } // Файл: app/Models/User.php 'datetime', 'date_of_birth' => 'date', 'is_active' => 'boolean', ]; public function subjectRegistrations() { return $this->hasMany(SubjectRegistration::class); } public function testSessions() { return $this->hasMany(TestSession::class); } public function researchPapers() { return $this->hasMany(ResearchPaper::class); } public function videoSessionsAsJudge() { return $this->hasMany(VideoSession::class, 'judge_id'); } public function judgeScores() { return $this->hasMany(JudgeScore::class, 'judge_id'); } public function qualifiedLevels() { return $this->hasMany(QualifiedUser::class); } public function notifications() { return $this->hasMany(Notification::class); } public function leaderboardEntries() { return $this->hasMany(Leaderboard::class); } public function isAdmin() { return $this->role === 'admin'; } public function isJudge() { return $this->role === 'judge' || $this->isAdmin(); } public function isStudent() { return $this->role === 'student'; } public function getFullNameAttribute() { return "{$this->first_name} {$this->last_name}"; } } // Файл: app/Policies/ResearchPaperPolicy.php id === $researchPaper->user_id || $user->isAdmin() || $user->isJudge(); } public function create(User $user) { return $user->isStudent(); } public function update(User $user, ResearchPaper $researchPaper) { return $user->id === $researchPaper->user_id; } public function delete(User $user, ResearchPaper $researchPaper) { return $user->id === $researchPaper->user_id || $user->isAdmin(); } } // Файл: app/Policies/VideoSessionPolicy.php id === $videoSession->judge_id || $user->id === $videoSession->researchPaper->user_id || $user->isAdmin(); } public function create(User $user) { return $user->isJudge() || $user->isAdmin(); } public function update(User $user, VideoSession $videoSession) { return $user->id === $videoSession->judge_id || $user->isAdmin(); } public function delete(User $user, VideoSession $videoSession) { return $user->id === $videoSession->judge_id || $user->isAdmin(); } public function join(User $user, VideoSession $videoSession) { return $user->id === $videoSession->judge_id || $user->id === $videoSession->researchPaper->user_id; } } // Файл: app/Providers/AppServiceProvider.php ResearchPaperPolicy::class, VideoSession::class => VideoSessionPolicy::class, ]; public function boot() { $this->registerPolicies(); } } // Файл: app/Providers/RouteServiceProvider.php configureRateLimiting(); $this->routes(function () { Route::prefix('api') // Убедитесь, что префикс 'api' соответствует вашей структуре URL ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); }); } /** * Configure the rate limiters for the application. */ protected function configureRateLimiting(): void { RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); }); } } // Файл: routes/api.php group(function () { // Auth Route::post('/logout', [AuthController::class, 'logout']); Route::get('/user', [AuthController::class, 'user']); // Tests Route::get('/tests', [TestController::class, 'index']); Route::get('/tests/{test}', [TestController::class, 'show']); Route::post('/tests/{test}/start', [TestSessionController::class, 'start']); // Test Sessions Route::post('/sessions/{session}/answer', [TestSessionController::class, 'submitAnswer']); Route::post('/sessions/{session}/finish', [TestSessionController::class, 'finish']); Route::get('/sessions/{session}/results', [TestSessionController::class, 'results']); // Subjects Route::get('/subjects', [SubjectController::class, 'index']); Route::post('/subjects/{subject}/register', [SubjectController::class, 'register']); // Leaderboard Route::get('/leaderboard', [LeaderboardController::class, 'index']); // User Profile Route::get('/profile', [UserController::class, 'profile']); Route::put('/profile', [UserController::class, 'update']); // Research Papers Route::apiResource('research-papers', ResearchPaperController::class); // Video Sessions Route::apiResource('video-sessions', VideoSessionController::class); Route::post('/video-sessions/{session}/join', [VideoSessionController::class, 'join']); // Questions (Admin only) Route::middleware('admin')->group(function () { Route::apiResource('questions', QuestionController::class); }); });