diff --git a/admin/src/App.vue b/admin/src/App.vue index b4c6be6a..f983fa8d 100644 --- a/admin/src/App.vue +++ b/admin/src/App.vue @@ -35,6 +35,9 @@ Exam + + Exam user + @@ -76,6 +79,7 @@ export default { } }) router.beforeEach((to, from, next) => { + console.log("App beforeEach to", to) if (to.path == '/login') { // 如果路径是 /login 则正常执行 next() diff --git a/admin/src/components/Header.vue b/admin/src/components/Header.vue index 67c671d5..5eeb9463 100644 --- a/admin/src/components/Header.vue +++ b/admin/src/components/Header.vue @@ -29,26 +29,32 @@ + + diff --git a/admin/src/views/user/form.vue b/admin/src/views/user/form.vue index f9a89a8d..962c72c6 100644 --- a/admin/src/views/user/form.vue +++ b/admin/src/views/user/form.vue @@ -1,3 +1,97 @@ + + + + diff --git a/admin/src/views/user/index.vue b/admin/src/views/user/index.vue index 1b4eb035..8368af0d 100644 --- a/admin/src/views/user/index.vue +++ b/admin/src/views/user/index.vue @@ -1,3 +1,184 @@ + + + + diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php index 94746cc1..5fa69f15 100644 --- a/app/Console/Commands/Test.php +++ b/app/Console/Commands/Test.php @@ -2,6 +2,8 @@ namespace App\Console\Commands; +use App\Models\User; +use App\Repositories\ExamRepository; use Illuminate\Console\Command; use Illuminate\Support\Facades\Hash; @@ -38,6 +40,9 @@ class Test extends Command */ public function handle() { - dd(strlen(Hash::make('123456'))); + $examRep = new ExamRepository(); + $user = User::query()->find(1); + $r = $examRep->assignToUser($user->id); + dd($r); } } diff --git a/app/Http/Controllers/ExamController.php b/app/Http/Controllers/ExamController.php index 2ee6fb03..f01bdaf0 100644 --- a/app/Http/Controllers/ExamController.php +++ b/app/Http/Controllers/ExamController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Http\Resources\ExamResource; +use App\Http\Resources\ExamUserResource; use App\Http\Resources\UserResource; use App\Repositories\ExamRepository; use App\Repositories\UserRepository; @@ -88,11 +89,12 @@ class ExamController extends Controller * Remove the specified resource from storage. * * @param int $id - * @return \Illuminate\Http\Response + * @return array */ public function destroy($id) { - // + $result = $this->repository->delete($id); + return $this->success($result, 'Delete exam success!'); } public function indexes() @@ -101,4 +103,11 @@ class ExamController extends Controller return $this->success($result); } + public function users(Request $request) + { + $result = $this->repository->listUser($request->all()); + $resource = ExamUserResource::collection($result); + return $this->success($resource); + } + } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 85a71b57..b0346663 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -2,9 +2,12 @@ namespace App\Http\Controllers; +use App\Http\Resources\ExamResource; use App\Http\Resources\UserResource; +use App\Repositories\ExamRepository; use App\Repositories\UserRepository; use Illuminate\Http\Request; +use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Facades\Auth; class UserController extends Controller @@ -109,4 +112,13 @@ class UserController extends Controller $resource = new UserResource($result); return $this->success($resource); } + + public function exams() + { + $id = Auth::id(); + $examRepository = new ExamRepository(); + $result = $examRepository->listMatchExam($id); + $resource = ExamResource::collection($result); + return $this->success($resource); + } } diff --git a/app/Http/Resources/ExamUserResource.php b/app/Http/Resources/ExamUserResource.php new file mode 100644 index 00000000..5a76a3d7 --- /dev/null +++ b/app/Http/Resources/ExamUserResource.php @@ -0,0 +1,30 @@ + $this->id, + 'status' => $this->status, + 'status_text' => $this->statusText, + 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + 'user' => new UserResource($this->whenLoaded('user')), + 'exam' => new ExamResource($this->whenLoaded('exam')), + ]; + } + +} diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php index f8baf1f7..6761d45b 100644 --- a/app/Http/Resources/UserResource.php +++ b/app/Http/Resources/UserResource.php @@ -21,9 +21,13 @@ class UserResource extends JsonResource 'status' => $this->status, 'added' => $this->added, 'class' => $this->class, + 'class_text' => $this->class_text, 'avatar' => $this->avatar, 'uploaded' => $this->uploaded, + 'uploaded_text' => mksize($this->uploaded), 'downloaded' => $this->downloaded, + 'downloaded_text' => mksize($this->downloaded), + 'bonus' => $this->seedbonus, 'seedtime' => $this->seedtime, 'leechtime' => $this->leechtime, ]; diff --git a/app/Models/AgentAllow.php b/app/Models/AgentAllow.php index 129c463f..d9979592 100644 --- a/app/Models/AgentAllow.php +++ b/app/Models/AgentAllow.php @@ -6,6 +6,8 @@ class AgentAllow extends NexusModel { protected $table = 'agent_allowed_family'; + public $timestamps = true; + protected $fillable = [ 'family', 'start_name', 'exception', 'allowhttps', 'comment', 'peer_id_pattern', 'peer_id_match_num', 'peer_id_matchtype', 'peer_id_start', diff --git a/app/Models/Exam.php b/app/Models/Exam.php index 01f54222..349b6370 100644 --- a/app/Models/Exam.php +++ b/app/Models/Exam.php @@ -6,6 +6,8 @@ class Exam extends NexusModel { protected $fillable = ['name', 'description', 'begin', 'end', 'status', 'filters', 'indexes']; + public $timestamps = true; + protected $casts = [ 'filters' => 'object', 'indexes' => 'array', diff --git a/app/Models/ExamProgress.php b/app/Models/ExamProgress.php index ab866292..e4050ca7 100644 --- a/app/Models/ExamProgress.php +++ b/app/Models/ExamProgress.php @@ -5,4 +5,6 @@ namespace App\Models; class ExamProgress extends NexusModel { protected $fillable = ['exam_id', 'uid', 'type_id', 'value']; + + public $timestamps = true; } diff --git a/app/Models/ExamUser.php b/app/Models/ExamUser.php new file mode 100644 index 00000000..6ee855af --- /dev/null +++ b/app/Models/ExamUser.php @@ -0,0 +1,39 @@ + ['text' => 'Normal'], + self::STATUS_FINISHED => ['text' => 'Finished'], + ]; + + protected $casts = [ + 'result' => 'json' + ]; + + public function getStatusTextAttribute() + { + return self::$status[$this->status]['text'] ?? ''; + } + + public function exam(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(Exam::class, 'exam_id'); + } + + public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(User::class, 'uid'); + } + + +} diff --git a/app/Models/NexusModel.php b/app/Models/NexusModel.php index 7ba9649c..e4f43e95 100644 --- a/app/Models/NexusModel.php +++ b/app/Models/NexusModel.php @@ -11,6 +11,8 @@ class NexusModel extends Model public $timestamps = false; + protected $perPage = 2; + /** * 为数组 / JSON 序列化准备日期。 * diff --git a/app/Models/User.php b/app/Models/User.php index a8f8fcab..73a755a2 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -54,6 +54,8 @@ class User extends Authenticatable self::CLASS_STAFF_LEADER => ['text' => 'Staff Leader'], ]; + protected $perPage = 2; + public function getClassTextAttribute(): string { return self::$classes[$this->class]['text'] ?? ''; @@ -96,4 +98,21 @@ class User extends Authenticatable protected $casts = [ ]; + + protected $dates = [ + 'added' + ]; + + + public function exams(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + { + return $this->belongsToMany(Exam::class, 'exam_users', 'uid', 'exam_id')->withTimestamps(); + } + + public function examDetails(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(ExamUser::class. 'uid'); + } + + } diff --git a/app/Repositories/ExamRepository.php b/app/Repositories/ExamRepository.php index e0eeb22a..f944f39f 100644 --- a/app/Repositories/ExamRepository.php +++ b/app/Repositories/ExamRepository.php @@ -2,6 +2,7 @@ namespace App\Repositories; use App\Models\Exam; +use App\Models\ExamUser; use App\Models\Setting; use App\Models\User; use Carbon\Carbon; @@ -20,27 +21,47 @@ class ExamRepository extends BaseRepository public function store(array $params) { - $data = Arr::only($params, ['name', 'description', 'status', 'filters']); - if (!empty($params['begin'])) { - - } + $this->checkIndexes($params); $exam = Exam::query()->create($params); return $exam; } public function update(array $params, $id) { + $this->checkIndexes($params); $exam = Exam::query()->findOrFail($id); $exam->update($params); return $exam; } + private function checkIndexes(array $params) + { + if (empty($params['indexes'])) { + throw new \InvalidArgumentException("Require index."); + } + $validIndex = array_filter($params['indexes'], function ($value) { + return isset($value['checked']) && $value['checked'] + && isset($value['require_value']) && $value['require_value'] > 0; + }); + if (empty($validIndex)) { + throw new \InvalidArgumentException("Require valid index."); + } + return true; + } + public function getDetail($id) { $exam = Exam::query()->findOrFail($id); return $exam; } + public function delete($id) + { + $exam = Exam::query()->findOrFail($id); + $result = $exam->delete(); + return $result; + } + public function listIndexes() { $out = []; @@ -51,52 +72,122 @@ class ExamRepository extends BaseRepository return $out; } - public function listFilters() + /** + * list valid exams + * + * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection + */ + public function listValid() { - $out = []; - foreach(Exam::$filters as $key => $value) { - $value['filter'] = $key; - $out[] = $value; - } - return $out; + $now = Carbon::now(); + return Exam::query() + ->where('begin', '<=', $now) + ->where('end', '>=', $now) + ->where('status', Exam::STATUS_ENABLED) + ->get(); } /** * list user match exams * * @param $uid - * @return array + * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection */ - public function listMatchExam($uid): array + public function listMatchExam($uid) { - $now = Carbon::now(); - $user = User::query()->findOrFail($uid, ['id', 'username', 'added', 'class']); - $exams = Exam::query() - ->where('begin', '<=', $now) - ->where('end', '>=', $now) - ->where('status', Exam::STATUS_ENABLED) - ->get(); - $result = []; $logPrefix = "uid: $uid"; - foreach ($exams as $exam) { - $filters = $exam->filters; - if (!in_array($user->class, $filters['classes'])) { - Log::info("$logPrefix, class: {$user->class} not in: " . json_encode($filters)); - continue; - } - $added = $user->added->toDateTimeString(); - if (!empty($filters['register_time_begin']) && $added < $filters['register_time_begin']) { - Log::info("$logPrefix, added: $added not after: " . $filters['register_time_begin']); - continue; - } - if (!empty($filters['register_time_end']) && $added > $filters['register_time_end']) { - Log::info("$logPrefix, added: $added not before: " . $filters['register_time_end']); - continue; - } - $result[] = $exam; + $exams = $this->listValid(); + if ($exams->isEmpty()) { + Log::info("$logPrefix, no valid exam."); + return $exams; } + $user = User::query()->findOrFail($uid, ['id', 'username', 'added', 'class']); + + $filtered = $exams->filter(function (Exam $exam) use ($user, $logPrefix) { + $filters = $exam->filters; + if (empty($filters->classes)) { + Log::info("$logPrefix, exam: {$exam->id} no class"); + return false; + } + if (!in_array($user->class, $filters->classes)) { + Log::info("$logPrefix, user class: {$user->class} not in: " . json_encode($filters)); + return false; + } + + $added = $user->added->toDateTimeString(); + $registerTimeBegin = $filters->register_time_range[0] ? Carbon::parse($filters->register_time_range[0])->toDateString() : ''; + $registerTimeEnd = $filters->register_time_range[1] ? Carbon::parse($filters->register_time_range[1])->toDateString() : ''; + if (empty($registerTimeBegin)) { + Log::info("$logPrefix, exam: {$exam->id} no register_time_begin"); + return false; + } + if ($added < $registerTimeBegin) { + Log::info("$logPrefix, added: $added not after: " . $registerTimeBegin); + return false; + } + + if (empty($registerTimeEnd)) { + Log::info("$logPrefix, exam: {$exam->id} no register_time_end"); + return false; + } + if ($added > $registerTimeEnd) { + Log::info("$logPrefix, added: $added not before: " . $registerTimeEnd); + return false; + } + + return true; + }); + + return $filtered; + } + + /** + * assign exam to user + * + * @param $uid + * @param int $examId + * @return mixed + */ + public function assignToUser($uid, $examId = 0) + { + if ($examId > 0) { + $exam = Exam::query()->find($examId); + } else { + $exams = $this->listMatchExam($uid); + if ($exams->count() > 1) { + throw new \LogicException("Match exam more than 1."); + } + $exam = $exams->first(); + } + if (!$exam) { + throw new \LogicException("No valid exam."); + } + $user = User::query()->findOrFail($uid); + $exists = $user->exams()->where('exam_id', $exam->id)->exists(); + if ($exists) { + throw new \LogicException("exam: {$exam->id} already assign to user: {$user->id}"); + } + $result = $user->exams()->save($exam); return $result; } + public function listUser(array $params) + { + $query = ExamUser::query(); + if (!empty($params['uid'])) { + $query->where('uid', $params['uid']); + } + if (!empty($params['exam_id'])) { + $query->where('exam_id', $params['exam_id']); + } + list($sortField, $sortType) = $this->getSortFieldAndType($params); + $query->orderBy($sortField, $sortType); + $result = $query->with(['user', 'exam'])->paginate(); + return $result; + + } + + + } diff --git a/database/migrations/2021_04_24_084104_create_exam_users_table.php b/database/migrations/2021_04_24_084104_create_exam_users_table.php new file mode 100644 index 00000000..a07c1216 --- /dev/null +++ b/database/migrations/2021_04_24_084104_create_exam_users_table.php @@ -0,0 +1,35 @@ +id(); + $table->integer('uid')->index(); + $table->integer('exam_id')->index(); + $table->integer('status')->default(0); + $table->text('result')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('exam_users'); + } +} diff --git a/routes/api.php b/routes/api.php index 9fe07975..c92c4a5a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,7 +19,9 @@ Route::group(['middleware' => ['auth:sanctum']], function () { Route::resource('agent-allow', \App\Http\Controllers\AgentAllowController::class); Route::resource('user', \App\Http\Controllers\UserController::class); Route::get('user-base', [\App\Http\Controllers\UserController::class, 'base']); + Route::get('user-exams', [\App\Http\Controllers\UserController::class, 'exams']); Route::resource('exam', \App\Http\Controllers\ExamController::class); + Route::get('exam-users', [\App\Http\Controllers\ExamController::class, 'users']); Route::get('exam-index', [\App\Http\Controllers\ExamController::class, 'indexes']); Route::get('class', [\App\Http\Controllers\UserController::class, 'classes']); });