You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
6.2 KiB

<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Models\Competition;
use App\Models\CompetitionAdmin;
use App\Support\BizDateTime;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\Rule;
class CompetitionAdminController extends Controller
{
public function index(Request $request, Competition $competition): JsonResponse
{
$data = $request->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<string, mixed>
*/
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<string, mixed>
*/
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() ? '········' : '—',
];
}
}