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.

281 lines
9.3 KiB

<?php
namespace App\Http\Controllers\Miniapp;
use App\Http\Controllers\Miniapp\BaseController;
use App\Models\Course;
use App\Models\CourseSignup;
use App\Models\MiniappUser;
use App\Support\ApiResponse;
use App\Support\Miniapp\MiniappPresenter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CourseController extends BaseController
{
use ApiResponse;
public function index(Request $request): JsonResponse
{
$user = $this->optionalUser($request);
$pageSize = (int) $request->query('page_size', 20);
$page = max(1, (int) $request->query('page', 1));
if ($request->filled('progress_status')) {
$statusFilter = (int) $request->query('progress_status');
$courses = $this->publishedQuery($request)
->orderByDesc('teach_start_date')
->orderByDesc('id')
->get()
->filter(fn (Course $course) => MiniappPresenter::resolveCourseProgressStatus($course) === $statusFilter)
->values();
$total = $courses->count();
$items = $courses
->slice(($page - 1) * $pageSize, $pageSize)
->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course, $user))
->values()
->all();
return $this->ok([
'items' => $items,
'meta' => [
'current_page' => $page,
'per_page' => $pageSize,
'total' => $total,
'last_page' => max(1, (int) ceil($total / $pageSize)),
],
]);
}
$paginator = $this->publishedQuery($request)
->orderByDesc('teach_start_date')
->orderByDesc('id')
->paginate($pageSize)
->withQueryString();
$paginator->getCollection()->transform(
fn (Course $course) => MiniappPresenter::serializeCourseList($course, $user)
);
return $this->paginated($paginator);
}
public function hot(): JsonResponse
{
$items = Course::query()
->with(['courseSystemItem', 'coverMedia', 'promoMedia'])
->withCount('signups')
->where('published', 1)
->get()
->filter(fn (Course $course) => in_array(
MiniappPresenter::resolveCourseProgressStatus($course),
[1, 2],
true
))
->sort(function (Course $a, Course $b) {
$signupDelta = ((int) $b->signups_count) <=> ((int) $a->signups_count);
if ($signupDelta !== 0) {
return $signupDelta;
}
return $b->id <=> $a->id;
})
->take(3)
->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course))
->values()
->all();
return $this->ok(['items' => $items]);
}
public function latest(): JsonResponse
{
$items = Course::query()
->with(['courseSystemItem', 'coverMedia', 'promoMedia'])
->withCount('signups')
->where('published', 1)
->get()
->filter(fn (Course $course) => in_array(
MiniappPresenter::resolveCourseProgressStatus($course),
[1, 2],
true
))
->sort(function (Course $a, Course $b) {
$aDate = $a->teach_start_date;
$bDate = $b->teach_start_date;
if ($aDate === null && $bDate === null) {
return $b->id <=> $a->id;
}
if ($aDate === null) {
return 1;
}
if ($bDate === null) {
return -1;
}
$dateDelta = $bDate <=> $aDate;
if ($dateDelta !== 0) {
return $dateDelta;
}
return $b->id <=> $a->id;
})
->take(3)
->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course))
->values()
->all();
return $this->ok(['items' => $items]);
}
public function show(Request $request, int $course): JsonResponse
{
$user = $this->optionalUser($request);
$model = Course::query()
->with(['courseSystemItem', 'coverMedia', 'promoMedia'])
->withCount('signups')
->where('published', 1)
->findOrFail($course);
return $this->ok(MiniappPresenter::serializeCourseList($model, $user));
}
public function signup(Request $request, int $course): JsonResponse
{
/** @var MiniappUser $user */
$user = $request->user();
$model = Course::query()->withCount('signups')->where('published', 1)->findOrFail($course);
if (CourseSignup::query()->where('course_id', $course)->where('miniapp_user_id', $user->id)->exists()) {
return $this->fail('您已报名该课程', 422);
}
if (! MiniappPresenter::canSignupCourse($model, (int) $model->signups_count)) {
return $this->fail('当前不可报名', 422);
}
$data = $request->validate([
'name' => ['required', 'string', 'max:64'],
'mobile' => ['required', 'string', 'max:32'],
'company' => ['nullable', 'string', 'max:128'],
]);
CourseSignup::query()->create([
'course_id' => $course,
'miniapp_user_id' => $user->id,
'name' => $data['name'],
'mobile' => $data['mobile'],
'company' => $data['company'] ?? null,
'signed_up_at' => now(),
'status' => 1,
]);
if (! $user->name) {
$user->name = $data['name'];
}
if (! $user->mobile) {
$user->mobile = $data['mobile'];
}
if (! $user->company && ! empty($data['company'])) {
$user->company = $data['company'];
}
$user->save();
return $this->ok(null, '报名成功');
}
public function mySignups(Request $request): JsonResponse
{
/** @var MiniappUser $user */
$user = $request->user();
$courseIds = CourseSignup::query()
->where('miniapp_user_id', $user->id)
->pluck('course_id');
$items = Course::query()
->with(['courseSystemItem', 'coverMedia', 'promoMedia'])
->withCount('signups')
->whereIn('id', $courseIds)
->orderByDesc('teach_start_date')
->orderByDesc('id')
->get()
->map(fn (Course $course) => MiniappPresenter::serializeCourseList($course, $user))
->values()
->all();
return $this->ok(['items' => $items]);
}
public function calendar(Request $request): JsonResponse
{
$data = $request->validate([
'month' => ['required', 'date_format:Y-m'],
]);
$courses = Course::query()
->with(['courseSystemItem'])
->withCount('signups')
->where('published', 1)
->get();
[$year, $month] = array_map('intval', explode('-', $data['month']));
$monthStart = sprintf('%04d-%02d-01', $year, $month);
$monthEnd = date('Y-m-t', strtotime($monthStart));
$events = $courses->map(function (Course $course) {
$progressStatus = MiniappPresenter::resolveCourseProgressStatus($course);
return [
'id' => $course->id,
'title' => $course->title,
'start_date' => $course->teach_start_date?->toDateString(),
'end_date' => $course->teach_end_date?->toDateString(),
'time_range' => MiniappPresenter::timeRange(
MiniappPresenter::formatTimeValue($course->teach_start_time),
MiniappPresenter::formatTimeValue($course->teach_end_time)
),
'location' => $course->location,
'progress_status' => $progressStatus,
'progress_status_label' => MiniappPresenter::progressStatusLabel($progressStatus),
];
})->filter(function (array $event) use ($monthStart, $monthEnd) {
if (! $event['start_date']) {
return false;
}
$start = $event['start_date'];
$end = $event['end_date'] ?: $start;
return $start <= $monthEnd && $end >= $monthStart;
})->values();
return $this->ok([
'month' => $data['month'],
'events' => $events,
]);
}
protected function publishedQuery(Request $request)
{
$query = Course::query()
->with(['courseSystemItem', 'coverMedia', 'promoMedia'])
->withCount('signups')
->where('published', 1);
if ($kw = $request->query('keyword')) {
$query->where(function ($q) use ($kw) {
$q->where('title', 'like', "%{$kw}%")
->orWhere('location', 'like', "%{$kw}%");
});
}
if ($request->filled('course_system_dict_item_id')) {
$query->where('course_system_dict_item_id', (int) $request->query('course_system_dict_item_id'));
}
return $query;
}
}