|
|
<?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, '仅可操作已绑定场馆');
|
|
|
}
|
|
|
}
|