166 lines
5.7 KiB
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('/api/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('/api/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('/api/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('/api/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('/api/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('/api/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);
|
|
}
|
|
}
|