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.

202 lines
7.8 KiB

5 days ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Activity;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class ActivityController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Activity::with('venue:id,name')->orderByDesc('id');
$this->restrictByVenue($request, $query);
if ($request->filled('keyword')) {
$keyword = trim((string) $request->input('keyword'));
$query->where('title', 'like', "%{$keyword}%");
}
if ($request->filled('venue_id')) {
$query->where('venue_id', (int) $request->input('venue_id'));
}
if ($request->filled('is_active')) {
$query->where('is_active', (bool) $request->boolean('is_active'));
}
$pageSize = max(1, min(100, (int) $request->input('page_size', 10)));
return response()->json($query->paginate($pageSize));
}
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'venue_id' => ['required', 'integer', 'exists:venues,id'],
'title' => ['required', 'string', 'max:150'],
'summary' => ['nullable', 'string', 'max:255'],
'category' => ['nullable', 'string', 'max:50'],
'quota' => ['nullable', 'integer', 'min:0'],
'start_at' => ['nullable', 'date'],
'end_at' => ['nullable', 'date'],
'address' => ['nullable', 'string', 'max:255'],
'lat' => ['nullable', 'numeric'],
'lng' => ['nullable', 'numeric'],
'detail_html' => ['nullable', 'string'],
'cover_image' => ['nullable', 'string', 'max:255'],
'gallery_media' => ['nullable', 'array'],
'gallery_media.*.type' => ['required_with:gallery_media', 'in:image,video'],
'gallery_media.*.url' => ['required_with:gallery_media', 'string', 'max:255'],
'tags' => ['nullable', 'array'],
'tags.*' => ['string', 'max:50'],
'reservation_notice' => ['nullable', 'string'],
'open_time' => ['nullable', 'string'],
'sort' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
$this->ensureVenuePermission($request, (int) $data['venue_id']);
if (!$request->user()?->isSuperAdmin()) {
unset($data['sort']);
}
$this->normalizeActivityDates($data);
$activity = Activity::create($data + [
'quota' => $data['quota'] ?? 0,
'tags' => array_values($data['tags'] ?? []),
'sort' => $data['sort'] ?? 0,
'is_active' => $data['is_active'] ?? true,
]);
return response()->json($activity->load('venue:id,name'), 201);
}
public function update(Request $request, Activity $activity): JsonResponse
{
$this->ensureVenuePermission($request, $activity->venue_id);
$data = $request->validate([
'venue_id' => ['sometimes', 'integer', 'exists:venues,id'],
'title' => ['sometimes', 'string', 'max:150'],
'summary' => ['sometimes', 'nullable', 'string', 'max:255'],
'category' => ['nullable', 'string', 'max:50'],
'quota' => ['sometimes', 'integer', 'min:0'],
'start_at' => ['nullable', 'date'],
'end_at' => ['nullable', 'date'],
'address' => ['nullable', 'string', 'max:255'],
'lat' => ['nullable', 'numeric'],
'lng' => ['nullable', 'numeric'],
'detail_html' => ['nullable', 'string'],
'cover_image' => ['nullable', 'string', 'max:255'],
'gallery_media' => ['nullable', 'array'],
'gallery_media.*.type' => ['required_with:gallery_media', 'in:image,video'],
'gallery_media.*.url' => ['required_with:gallery_media', 'string', 'max:255'],
'tags' => ['sometimes', 'nullable', 'array'],
'tags.*' => ['string', 'max:50'],
'reservation_notice' => ['nullable', 'string'],
'open_time' => ['nullable', 'string'],
'sort' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
if (array_key_exists('venue_id', $data)) {
$this->ensureVenuePermission($request, (int) $data['venue_id']);
}
if (!$request->user()?->isSuperAdmin()) {
unset($data['sort']);
}
$this->normalizeActivityDates($data);
$activity->fill($data)->save();
if (array_key_exists('tags', $data)) {
$activity->tags = array_values($data['tags'] ?? []);
$activity->save();
}
return response()->json($activity->fresh()->load('venue:id,name'));
}
public function toggle(Request $request, Activity $activity): JsonResponse
{
$this->ensureVenuePermission($request, $activity->venue_id);
$activity->is_active = !$activity->is_active;
$activity->save();
return response()->json($activity->fresh()->load('venue:id,name'));
}
public function destroy(Request $request, Activity $activity): JsonResponse
{
$this->ensureVenuePermission($request, $activity->venue_id);
$count = $activity->reservations()->count();
if ($count > 0) {
return response()->json([
'message' => '该活动已有报名记录,不能删除',
'reservation_count' => $count,
], 422);
}
$activity->delete();
return response()->json(['message' => '删除成功']);
}
public function restore(Request $request, int $activityId): JsonResponse
{
$activity = Activity::withTrashed()->findOrFail($activityId);
$this->ensureVenuePermission($request, $activity->venue_id);
$disableAfterRestore = $request->boolean('disable_after_restore');
$activity->restore();
if ($disableAfterRestore) {
$activity->is_active = false;
$activity->save();
}
return response()->json($activity->fresh()->load('venue:id,name'));
}
/**
* 开始/结束仅传年月日时:开始为当日 00:00:00结束为当日 23:59:59允许同一天。
*
* @param array<string, mixed> $data
*/
private function normalizeActivityDates(array &$data): void
{
if (array_key_exists('start_at', $data)) {
if ($data['start_at'] === null || $data['start_at'] === '') {
$data['start_at'] = null;
} else {
$data['start_at'] = Carbon::parse($data['start_at'])->startOfDay();
}
}
if (array_key_exists('end_at', $data)) {
if ($data['end_at'] === null || $data['end_at'] === '') {
$data['end_at'] = null;
} else {
$data['end_at'] = Carbon::parse($data['end_at'])->endOfDay();
}
}
if (!empty($data['start_at']) && !empty($data['end_at']) && $data['end_at']->lt($data['start_at'])) {
throw ValidationException::withMessages(['end_at' => ['结束日期不能早于开始日期']]);
}
}
private function restrictByVenue(Request $request, $query): void
{
$user = $request->user();
if ($user->isSuperAdmin()) {
return;
}
$query->whereIn('venue_id', $user->venues()->pluck('venues.id'));
}
private function ensureVenuePermission(Request $request, int $venueId): void
{
$user = $request->user();
if ($user->isSuperAdmin()) {
return;
}
$allowed = $user->venues()->where('venues.id', $venueId)->exists();
abort_unless($allowed, 403, '仅可操作已绑定场馆');
}
}