exists('withDetails'); return Course::query() ->select($withDetails ? ['id', 'name', 'description', 'lecturer_id'] : ['id', 'name']) ->with('media') ->orderBy('name') ->when($withDetails, fn (Builder $query) => $query ->with('lecturer.media') ->withMin(['courseEvents as next_event' => fn (Builder $events) => $events->where('from', '>=', now())], 'from')) ->when($request->has('user_id'), fn (Builder $query) => $query->where('created_by', $request->integer('user_id'))) ->when( $request->search, fn (Builder $query) => $query ->whereLike('name', "%{$request->search}%") ) ->when( $request->exists('selected'), fn (Builder $query) => $query->whereIn('id', $this->numericIds($request)), fn (Builder $query) => $withDetails ? $query : $query->limit(10) ) ->get() ->map(function (Course $course) use ($withDetails) { $course->image = $course->getFirstMediaUrl('logo', 'thumb'); if (! $withDetails) { return $course; } return [ 'id' => $course->id, 'name' => $course->name, 'image' => $course->image, 'description' => $course->description, 'next_event' => $course->next_event, 'lecturer' => $this->lecturerSummary($course->lecturer), ]; }); } /** * Referenten-Kurzinfo für Kurs-Antworten (Liste und Detail). * * @return array|null */ private function lecturerSummary(?Lecturer $lecturer): ?array { if ($lecturer === null) { return null; } return [ 'id' => $lecturer->id, 'name' => $lecturer->name, 'subtitle' => $lecturer->subtitle, 'image' => $lecturer->getFirstMediaUrl('avatar', 'thumb'), ]; } /** * Kurs anlegen * * Erlaubt einem authentifizierten Referenten, einen Kurs programmatisch anzulegen. * Der Ersteller (created_by) wird automatisch auf den angemeldeten Nutzer gesetzt. */ #[ResponseAttribute(status: 403, description: 'Nur Referenten (is_lecturer) dürfen Kurse anlegen.')] public function store(StoreCourseRequest $request): JsonResponse { $course = Course::create($request->validated()); return CourseResource::make($course->fresh()) ->response() ->setStatusCode(Response::HTTP_CREATED); } /** * Kurs anzeigen * * Öffentlicher Endpunkt; liefert einen Kurs mit Beschreibung, Logo, Referent * und allen kommenden Kurs-Events (inkl. Veranstaltungsort und Stadt), * aufsteigend nach Beginn sortiert. * * @return array */ public function show(Course $course): array { $course->load([ 'lecturer.media', 'media', 'courseEvents' => fn ($query) => $query ->where('from', '>=', now()) ->orderBy('from') ->with('venue.city.country'), ]); return [ 'id' => $course->id, 'name' => $course->name, 'description' => $course->description, 'image' => $course->getFirstMediaUrl('logo', 'preview'), 'portalLink' => url()->route('courses.landingpage', [ 'country' => config('app.domain_country'), 'course' => $course, ]), 'lecturer' => $this->lecturerSummary($course->lecturer), 'events' => $course->courseEvents->map(fn (CourseEvent $event) => [ 'id' => $event->id, 'course_id' => $event->course_id, 'venue_id' => $event->venue_id, 'from' => $event->from, 'to' => $event->to, 'link' => $event->link, 'venue' => [ 'id' => $event->venue->id, 'name' => $event->venue->name, 'city' => [ 'id' => $event->venue->city->id, 'name' => $event->venue->city->name, 'country_id' => $event->venue->city->country_id, 'country' => [ 'id' => $event->venue->city->country->id, 'name' => $event->venue->city->country->name, 'code' => $event->venue->city->country->code, ], ], ], ])->all(), ]; } /** * Kurs aktualisieren * * Aktualisiert einen Kurs; nur für den Ersteller oder einen Super-Admin. */ #[ResponseAttribute(status: 403, description: 'Nur der Ersteller des Kurses oder ein Super-Admin darf ihn ändern.')] public function update(UpdateCourseRequest $request, Course $course): CourseResource { $course->update($request->validated()); return CourseResource::make($course->fresh()); } /** * Kurs-Logo hochladen * * Lädt ein Logo (multipart, Feld „file") in die singleFile-Collection „logo" und ersetzt * dabei ein vorhandenes Logo. Nur für den Ersteller oder einen Super-Admin. Die Antwort * enthält die frische Logo-URL. */ #[ResponseAttribute(status: 403, description: 'Nur der Ersteller oder ein Super-Admin darf das Logo ersetzen.')] #[ResponseAttribute(status: 422, description: 'Validierungsfehler (kein Bild, falscher MIME-Typ, zu groß oder zu große Abmessungen).')] public function uploadLogo(UploadMediaRequest $request, Course $course): CourseResource { $course->addMedia($request->file('file')->getRealPath()) ->usingName($course->name) ->toMediaCollection('logo'); return CourseResource::make($course->fresh()); } #[ExcludeRouteFromDocs] public function destroy(Course $course) { // } }