feat: harden admin auth and ticket workflows
- add rotating admin refresh tokens and password gates\n- expand ticket galleries, exports, and client resources\n- improve IP allocation auditing and dashboard data\n- enforce encrypted client access tokens on public routes
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
namespace App\Http\Controllers\Api\Client\Ticket;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\Ticket\TicketResource;
|
||||
use App\Http\Resources\Api\Ticket\TicketReplyResource;
|
||||
use App\Http\Resources\Api\Client\Ticket\ClientTicketReplyResource;
|
||||
use App\Http\Resources\Api\Client\Ticket\ClientTicketResource;
|
||||
use App\Jobs\Ticket\TicketJobs;
|
||||
use App\Mail\TicketCreated;
|
||||
use App\Mail\Admin\TicketAdminNotification;
|
||||
@@ -14,9 +14,9 @@ use App\Models\Notification;
|
||||
use App\Events\TicketReplyCreated;
|
||||
use App\Repositories\Ticket\TicketRepositoryInterface;
|
||||
use App\Repositories\TicketReply\TicketReplyRepositoryInterface;
|
||||
use App\Support\TicketGallery;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TicketController extends Controller
|
||||
@@ -26,27 +26,38 @@ class TicketController extends Controller
|
||||
protected TicketReplyRepositoryInterface $ticketReplyRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* List tickets for the authenticated customer (paginated, filterable by status).
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$filters = $request->validate([
|
||||
'status' => ['nullable', 'string', 'max:20'],
|
||||
'room' => ['nullable', 'string', 'max:50'],
|
||||
'search' => ['nullable', 'string', 'max:100'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:50'],
|
||||
]);
|
||||
|
||||
$customer = auth('client')->user();
|
||||
|
||||
$query = Ticket::query()
|
||||
->where('customer_id', $customer->id)
|
||||
->latest('id');
|
||||
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->input('status'));
|
||||
foreach (['status', 'room'] as $column) {
|
||||
if (filled($filters[$column] ?? null)) {
|
||||
$query->where($column, $filters[$column]);
|
||||
}
|
||||
}
|
||||
|
||||
$paginated = $query->paginate(15);
|
||||
if (filled($filters['search'] ?? null)) {
|
||||
$keyword = '%' . addcslashes($filters['search'], '%_\\') . '%';
|
||||
$query->where(fn ($q) => $q->where('subject', 'like', $keyword)->orWhere('ticket_num', 'like', $keyword));
|
||||
}
|
||||
|
||||
$paginated = $query->paginate($filters['per_page'] ?? 15);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'items' => TicketResource::collection($paginated->items()),
|
||||
'items' => ClientTicketResource::collection($paginated->items()),
|
||||
'total' => $paginated->total(),
|
||||
'per_page' => $paginated->perPage(),
|
||||
'current_page' => $paginated->currentPage(),
|
||||
@@ -73,11 +84,7 @@ class TicketController extends Controller
|
||||
|
||||
$ticketNum = 'TKT-' . now()->format('Ymd') . '-' . strtoupper(Str::random(5));
|
||||
|
||||
$gallery = [];
|
||||
foreach ($request->file('images', []) as $image) {
|
||||
$path = $image->store('tickets', 'public');
|
||||
$gallery[] = Storage::disk('public')->url($path);
|
||||
}
|
||||
$gallery = TicketGallery::store($request->file('images', []), $customer->id);
|
||||
|
||||
$ticket = $this->ticketRepository->create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
@@ -103,7 +110,7 @@ class TicketController extends Controller
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Ticket đã được khởi tạo.',
|
||||
'data' => new TicketResource($ticket),
|
||||
'data' => new ClientTicketResource($ticket),
|
||||
], 201);
|
||||
}
|
||||
|
||||
@@ -129,7 +136,7 @@ class TicketController extends Controller
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => new TicketResource($ticket),
|
||||
'data' => new ClientTicketResource($ticket),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -193,7 +200,7 @@ class TicketController extends Controller
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Phản hồi đã được gửi.',
|
||||
'data' => new TicketReplyResource($reply),
|
||||
'data' => new ClientTicketReplyResource($reply),
|
||||
], 201);
|
||||
}
|
||||
|
||||
@@ -228,7 +235,17 @@ class TicketController extends Controller
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Ticket đã được đóng.',
|
||||
'data' => new TicketResource($ticket->fresh()),
|
||||
'data' => new ClientTicketResource($ticket->fresh()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function image(string $ticket_num, int $index)
|
||||
{
|
||||
$ticket = Ticket::query()
|
||||
->where('ticket_num', $ticket_num)
|
||||
->where('customer_id', auth('client')->id())
|
||||
->firstOrFail();
|
||||
|
||||
return TicketGallery::response($ticket, $index);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user