validate([ 'page' => ['sometimes', 'integer', 'min:1'], 'per_page' => ['sometimes', 'integer', 'min:1', 'max:100'], 'keyword' => ['sometimes', 'nullable', 'string', 'max:100'], 'event_location' => ['sometimes', 'nullable', 'string', 'max:64'], ]); $query = $competition->competitionAdmins()->orderByDesc('id'); $keyword = trim((string) ($data['keyword'] ?? '')); $hasMobile = Schema::hasColumn('competition_admins', 'mobile'); $hasEventLocation = Schema::hasColumn('competition_admins', 'event_location'); if ($keyword !== '') { $like = '%'.str_replace(['%', '_'], ['\\%', '\\_'], $keyword).'%'; $query->where(function ($q) use ($like, $hasMobile): void { $q->where('username', 'like', $like) ->orWhere('name', 'like', $like); if ($hasMobile) { $q->orWhere('mobile', 'like', $like); } }); } $eventLocation = trim((string) ($data['event_location'] ?? '')); if ($eventLocation !== '' && $hasEventLocation) { $query->where('event_location', $eventLocation); } $perPage = min((int) ($data['per_page'] ?? 15), 100); $paginator = $query->paginate($perPage); $paginator->getCollection()->transform(fn (CompetitionAdmin $row) => $this->toRow($row)); return response()->json($paginator); } public function store(Request $request, Competition $competition): JsonResponse { $data = $this->validatedPayload($request, $competition); $payload = [ 'username' => $data['username'], 'password_hash' => $data['password'], 'name' => $data['name'], 'status' => $data['status'] ?? 'active', ]; if (Schema::hasColumn('competition_admins', 'mobile')) { $payload['mobile'] = $data['mobile'] ?? null; } if (Schema::hasColumn('competition_admins', 'event_location')) { $payload['event_location'] = $data['event_location'] ?? null; } $row = $competition->competitionAdmins()->create($payload); return response()->json($this->toRow($row), 201); } public function update(Request $request, Competition $competition, CompetitionAdmin $competition_admin): JsonResponse { $this->ensureBelongs($competition, $competition_admin); $data = $this->validatedPayload($request, $competition, (int) $competition_admin->id); $fill = [ 'username' => $data['username'], 'name' => $data['name'], 'status' => $data['status'] ?? $competition_admin->status, ]; if (Schema::hasColumn('competition_admins', 'mobile')) { $fill['mobile'] = array_key_exists('mobile', $data) ? $data['mobile'] : $competition_admin->mobile; } if (Schema::hasColumn('competition_admins', 'event_location')) { $fill['event_location'] = array_key_exists('event_location', $data) ? $data['event_location'] : $competition_admin->event_location; } $competition_admin->fill($fill); if (! empty($data['password'])) { $competition_admin->password_hash = $data['password']; } $competition_admin->save(); return response()->json($this->toRow($competition_admin->fresh())); } public function destroy(Competition $competition, CompetitionAdmin $competition_admin): JsonResponse { $this->ensureBelongs($competition, $competition_admin); $competition_admin->delete(); return response()->json(['message' => 'deleted']); } /** * @return array */ private function validatedPayload(Request $request, Competition $competition, ?int $ignoreId = null): array { return $request->validate([ 'username' => [ 'required', 'string', 'max:64', 'regex:/^[A-Za-z0-9._-]+$/', Rule::unique('competition_admins', 'username') ->where(fn ($q) => $q->where('competition_id', $competition->id)) ->ignore($ignoreId), ], 'name' => ['required', 'string', 'max:64'], 'mobile' => ['sometimes', 'nullable', 'string', 'max:20'], 'event_location' => [ 'sometimes', 'nullable', 'string', 'max:64', Rule::in(config('contest.event_locations', ['shanghai', 'suzhou', 'shenzhen'])), ], 'password' => [$ignoreId ? 'nullable' : 'required', 'string', 'min:6', 'max:255'], 'status' => ['sometimes', 'string', Rule::in(['active', 'disabled'])], ], [ 'username.regex' => '账号仅限字母、数字和 ._-', ]); } private function ensureBelongs(Competition $competition, CompetitionAdmin $admin): void { if (! $admin->belongsToCompetition((int) $competition->id)) { abort(404); } } /** * @return array */ private function toRow(CompetitionAdmin $row): array { return [ 'id' => $row->id, 'competition_id' => $row->competition_id, 'username' => $row->username, 'name' => $row->name, 'mobile' => $row->mobile, 'event_location' => $row->event_location, 'status' => $row->status, 'last_login_at' => BizDateTime::toIso8601($row->last_login_at), 'created_at' => BizDateTime::toIso8601($row->created_at), 'password_set' => $row->usesPassword(), 'password_display' => $row->usesPassword() ? '········' : '—', ]; } }