QuickQuiz/app/Http/Controllers/Api/Admin/QuestionBankController.php

166 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Api\Admin\Concerns\AuthorizesOwnedResources;
use App\Http\Controllers\Controller;
use App\Models\ExportJob;
use App\Models\OperationLog;
use App\Models\QuestionBank;
use App\Support\ApiResponse;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
#[Apidoc\Group('后台')]
#[Apidoc\Title('题库管理')]
#[Apidoc\RouteMiddleware(['jwt.auth'])]
final class QuestionBankController extends Controller
{
use AuthorizesOwnedResources;
#[Apidoc\Title('题库列表')]
#[Apidoc\Url('/admin/banks')]
#[Apidoc\Method('GET')]
#[Apidoc\RouteMiddleware(['permission:banks'])]
public function index(Request $request): JsonResponse
{
$query = $this->ownedBanksQuery($request)->withCount('questions')->latest();
if ($keyword = $request->query('keyword')) {
$query->where('name', 'like', '%'.$keyword.'%');
}
return ApiResponse::page($query->paginate((int) $request->query('per_page', 20)));
}
#[Apidoc\Title('创建题库')]
#[Apidoc\Url('/admin/banks')]
#[Apidoc\Method('POST')]
#[Apidoc\RouteMiddleware(['permission:banks.create'])]
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'name' => ['required', 'string', 'max:120'],
'description' => ['nullable', 'string'],
'visibility' => ['required', 'in:public,private,assigned'],
]);
$bank = QuestionBank::create($data + ['owner_id' => $request->user()->id, 'is_active' => true]);
OperationLog::create([
'user_id' => $request->user()->id,
'action' => 'bank.created',
'target_type' => QuestionBank::class,
'target_id' => $bank->id,
'ip' => $request->ip(),
'payload' => ['name' => $bank->name],
]);
return ApiResponse::success($bank, '题库已创建');
}
#[Apidoc\Title('更新题库')]
#[Apidoc\Url('/admin/banks/{bank}')]
#[Apidoc\Method('PUT')]
#[Apidoc\RouteMiddleware(['permission:banks.update'])]
public function update(Request $request, mixed $bank): JsonResponse
{
$bank = $this->resolveBank($bank);
$this->authorizeBankOwner($request, $bank);
$data = $request->validate([
'name' => ['sometimes', 'string', 'max:120'],
'description' => ['nullable', 'string'],
'visibility' => ['sometimes', 'in:public,private,assigned'],
'is_active' => ['sometimes', 'boolean'],
]);
$bank->update($data);
return ApiResponse::success($bank->fresh(), '题库已更新');
}
#[Apidoc\Title('删除题库')]
#[Apidoc\Url('/admin/banks/{bank}')]
#[Apidoc\Method('DELETE')]
#[Apidoc\RouteMiddleware(['permission:banks.delete'])]
public function destroy(Request $request, mixed $bank): JsonResponse
{
$bank = $this->resolveBank($bank);
$this->authorizeBankOwner($request, $bank);
$bank->delete();
OperationLog::create([
'user_id' => $request->user()->id,
'action' => 'bank.deleted',
'target_type' => QuestionBank::class,
'target_id' => $bank->id,
'ip' => $request->ip(),
]);
return ApiResponse::success(null, '题库已删除');
}
#[Apidoc\Title('题库授权')]
#[Apidoc\Url('/admin/banks/{bank}/shares')]
#[Apidoc\Method('POST')]
#[Apidoc\RouteMiddleware(['permission:banks.share'])]
public function share(Request $request, mixed $bank): JsonResponse
{
$bank = $this->resolveBank($bank);
$this->authorizeBankOwner($request, $bank);
$data = $request->validate([
'targets' => ['array'],
'targets.*.type' => ['required', 'in:user,class'],
'targets.*.id' => ['required', 'integer'],
]);
DB::table('bank_shares')->where('question_bank_id', $bank->id)->delete();
foreach ($data['targets'] ?? [] as $target) {
DB::table('bank_shares')->insert([
'question_bank_id' => $bank->id,
'target_type' => $target['type'],
'target_id' => $target['id'],
'created_at' => now(),
'updated_at' => now(),
]);
}
$bank->update(['visibility' => ($data['targets'] ?? []) === [] ? $bank->visibility : 'assigned']);
return ApiResponse::success(null, '授权已保存');
}
#[Apidoc\Title('题库导出')]
#[Apidoc\Url('/admin/banks/{bank}/export')]
#[Apidoc\Method('POST')]
#[Apidoc\RouteMiddleware(['permission:questions.export'])]
public function export(Request $request, mixed $bank): JsonResponse
{
$bank = $this->resolveBank($bank);
$this->authorizeBankOwner($request, $bank);
$payload = $bank->load('questions.options', 'categories', 'tags')->toArray();
$path = 'exports/bank-'.$bank->id.'-'.now()->format('YmdHis').'.json';
Storage::put($path, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
$job = ExportJob::create([
'user_id' => $request->user()->id,
'type' => 'question_bank',
'file_path' => $path,
'payload' => ['question_bank_id' => $bank->id],
]);
return ApiResponse::success($job, '题库已导出');
}
private function resolveBank(mixed $bank): QuestionBank
{
if ($bank instanceof QuestionBank && $bank->exists) {
return $bank;
}
return QuestionBank::query()->findOrFail((int) $bank);
}
}