97 خطوط
5.0 KiB
PHP
97 خطوط
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Courses\Http;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Modules\Courses\Application\CourseVersionComparison;
|
|
use App\Modules\Courses\Domain\Course;
|
|
use App\Modules\Courses\Domain\CourseVersion;
|
|
use App\Modules\Identity\Application\RolePermissions;
|
|
use App\Modules\Identity\Domain\Enums\Permission;
|
|
use App\Modules\Tenancy\Application\TenantContext;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
final class CourseVersionController extends Controller
|
|
{
|
|
public function __construct(private readonly TenantContext $tenant, private readonly RolePermissions $permissions, private readonly CourseVersionComparison $comparison) {}
|
|
|
|
public function index(Request $request, string $course): JsonResponse
|
|
{
|
|
$this->authorize($request);
|
|
$courseModel = $this->course($course);
|
|
$filters = $request->validate([
|
|
'search' => ['nullable', 'string', 'max:100'],
|
|
'status' => ['nullable', Rule::in(['draft', 'in_review', 'published'])],
|
|
'page' => ['nullable', 'integer', 'min:1'],
|
|
'perPage' => ['nullable', 'integer', 'min:1', 'max:50'],
|
|
]);
|
|
$base = CourseVersion::query()->where('organization_id', $this->tenant->id())->where('course_id', $courseModel->getKey());
|
|
$summary = [
|
|
'total' => (clone $base)->count(),
|
|
'published' => (clone $base)->where('status', 'published')->count(),
|
|
'drafts' => (clone $base)->whereIn('status', ['draft', 'in_review'])->count(),
|
|
];
|
|
$search = trim((string) ($filters['search'] ?? ''));
|
|
$versions = $base
|
|
->when($search !== '', function ($query) use ($search) {
|
|
$query->where(function ($nested) use ($search) {
|
|
$nested->where('title', 'like', '%'.$search.'%')->orWhere('description', 'like', '%'.$search.'%');
|
|
if (ctype_digit($search)) {
|
|
$nested->orWhere('version_number', (int) $search);
|
|
}
|
|
});
|
|
})
|
|
->when($filters['status'] ?? null, fn ($query, string $status) => $query->where('status', $status))
|
|
->with(['creator:id,name', 'publisher:id,name', 'modules.lessons.blocks', 'assessments.questions'])
|
|
->withCount(['modules', 'lessons', 'blocks', 'assessments'])
|
|
->orderByDesc('version_number')
|
|
->paginate($filters['perPage'] ?? 10);
|
|
|
|
return response()->json(['data' => [
|
|
'summary' => $summary,
|
|
'items' => $versions->getCollection()->map(fn (CourseVersion $version) => $this->payload($version))->values(),
|
|
'meta' => ['currentPage' => $versions->currentPage(), 'lastPage' => $versions->lastPage(), 'total' => $versions->total()],
|
|
]]);
|
|
}
|
|
|
|
public function compare(Request $request, string $course): JsonResponse
|
|
{
|
|
$this->authorize($request);
|
|
$courseModel = $this->course($course);
|
|
$data = $request->validate(['ids' => ['required', 'array', 'size:2'], 'ids.*' => ['required', 'string', 'distinct']]);
|
|
$versions = CourseVersion::query()->where('organization_id', $this->tenant->id())->where('course_id', $courseModel->getKey())->whereIn('id', $data['ids'])->get()->keyBy(fn (CourseVersion $version) => (string) $version->getKey());
|
|
abort_unless($versions->count() === 2, 422, 'نسخههای انتخابشده قابل مقایسه نیستند.');
|
|
$before = $versions->get($data['ids'][0]);
|
|
$after = $versions->get($data['ids'][1]);
|
|
|
|
return response()->json(['data' => $this->comparison->compare($before, $after)]);
|
|
}
|
|
|
|
private function payload(CourseVersion $version): array
|
|
{
|
|
$actor = $version->status->value === 'published' ? ($version->publisher ?? $version->creator) : $version->creator;
|
|
|
|
return [
|
|
'id' => $version->getKey(), 'number' => $version->version_number, 'status' => $version->status->value,
|
|
'title' => $version->title, 'description' => $version->description, 'sourceVersionId' => $version->source_version_id,
|
|
'createdAt' => $version->created_at?->toISOString(), 'updatedAt' => $version->updated_at?->toISOString(), 'publishedAt' => $version->published_at?->toISOString(),
|
|
'actor' => $actor ? ['id' => $actor->getKey(), 'name' => $actor->name] : null,
|
|
'moduleCount' => (int) $version->modules_count, 'lessonCount' => (int) $version->lessons_count,
|
|
'assessmentCount' => (int) $version->assessments_count, 'blockCount' => (int) $version->blocks_count,
|
|
'unpublishedChanges' => $this->comparison->unpublishedChangeCount($version),
|
|
];
|
|
}
|
|
|
|
private function course(string $id): Course
|
|
{
|
|
return Course::query()->where('organization_id', $this->tenant->id())->findOrFail($id);
|
|
}
|
|
|
|
private function authorize(Request $request): void
|
|
{
|
|
abort_unless($this->permissions->allows($request->user(), Permission::CoursesAuthor), 403);
|
|
}
|
|
}
|