authorize($request); return response()->json(['data' => $this->publication->readiness($this->version($course, $version))]); } public function configure(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); $data = $request->validate([ 'mode' => ['required', Rule::in(['all', 'any'])], 'rules' => ['required', 'array', 'min:1', 'max:8'], 'rules.*.type' => ['required', Rule::in(['all_required_lessons', 'minimum_lesson_percentage', 'assessment_passed', 'minimum_score', 'required_interaction'])], 'rules.*.value' => ['nullable', 'numeric', 'between:0,100'], ]); $model = $this->publication->configure($this->version($course, $version), $data); return response()->json(['data' => $this->payload($model)]); } public function submitReview(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); return response()->json(['data' => $this->payload($this->publication->submitReview($this->version($course, $version)))]); } public function returnDraft(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); return response()->json(['data' => $this->payload($this->publication->returnToDraft($this->version($course, $version)))]); } public function publish(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); $data = $request->validate(['reassignMode' => ['required', Rule::in(['none', 'everyone', 'selected'])], 'assignmentIds' => ['nullable', 'array'], 'assignmentIds.*' => ['string']]); $model = $this->version($course, $version); $published = $this->publication->publish($model, (string) $request->user()->getKey()); $created = $this->reassign($request, $published, $data['reassignMode'], $data['assignmentIds'] ?? []); return response()->json(['data' => [...$this->payload($published), 'reassignmentsCreated' => $created]]); } public function schedule(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); $data = $request->validate(['scheduledPublishAt' => ['required', 'date', 'after:now'], 'scheduledUnpublishAt' => ['nullable', 'date', 'after:scheduledPublishAt']]); $model = $this->publication->schedule($this->version($course, $version), new \DateTimeImmutable($data['scheduledPublishAt']), isset($data['scheduledUnpublishAt']) ? new \DateTimeImmutable($data['scheduledUnpublishAt']) : null); return response()->json(['data' => $this->payload($model)]); } public function cancelSchedule(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); return response()->json(['data' => $this->payload($this->publication->cancelSchedule($this->version($course, $version)))]); } public function unpublish(Request $request, string $course, string $version): JsonResponse { $this->authorize($request); $this->publication->unpublish($this->version($course, $version)); return response()->json(status: 204); } /** @param list $selected */ private function reassign(Request $request, CourseVersion $published, string $mode, array $selected): int { if ($mode === 'none' || ! $published->source_version_id) { return 0; } $query = Assignment::query()->where('organization_id', $this->tenant->id())->where('assignable_type', 'course')->where('assignable_id', $published->source_version_id)->where('status', 'active'); if ($mode === 'selected') { $query->whereIn('id', $selected); } return DB::transaction(function () use ($query, $request, $published): int { $count = 0; foreach ($query->get() as $source) { $copy = $source->replicate(['cancelled_at']); $copy->assignable_id = $published->getKey(); $copy->assigned_by = $request->user()->getKey(); $copy->source = 'version_reassignment'; $copy->save(); $this->resolver->sync($copy); $count++; } return $count; }); } private function version(string $course, string $version): CourseVersion { Course::query()->where('organization_id', $this->tenant->id())->findOrFail($course); return CourseVersion::query()->where('organization_id', $this->tenant->id())->where('course_id', $course)->findOrFail($version); } /** @return array */ private function payload(CourseVersion $version): array { return [ 'id' => $version->getKey(), 'status' => $version->status->value, 'completionRules' => $version->completion_rules, 'reviewSubmittedAt' => $version->review_submitted_at?->toISOString(), 'publishedAt' => $version->published_at?->toISOString(), 'scheduledPublishAt' => $version->scheduled_publish_at?->toISOString(), 'scheduledUnpublishAt' => $version->scheduled_unpublish_at?->toISOString(), 'unpublishedAt' => $version->unpublished_at?->toISOString(), 'taxonomySnapshotCount' => count($version->taxonomy_snapshot ?? []), ]; } private function authorize(Request $request): void { abort_unless($this->permissions->allows($request->user(), Permission::CoursesPublish), 403); } }