archetecture security fix
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\CompetitionWinners;
|
||||
|
||||
use App\Http\Controllers\Api\Core\BaseApiController;
|
||||
use App\Http\Requests\Admin\SaveCompetitionScoresRequest;
|
||||
use App\Http\Requests\Admin\StoreCompetitionWinnerRequest;
|
||||
use App\Http\Requests\Admin\UpdateCompetitionWinnerRequest;
|
||||
use App\Services\Admin\CompetitionWinners\CompetitionWinnersAdminService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CompetitionWinnersController extends BaseApiController
|
||||
{
|
||||
public function __construct(
|
||||
private CompetitionWinnersAdminService $service,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$payload = $this->service->indexData();
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'competitions' => array_values(array_map(
|
||||
fn ($row) => $this->normalizeCompetitionRow($this->rowToArray($row)),
|
||||
$this->iterableToArray($payload['competitions'] ?? [])
|
||||
)),
|
||||
'sectionMap' => $payload['sectionMap'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function createForm(): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
...$this->service->createFormData(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function settingsForm(int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$payload = $this->service->settingsFormData($id);
|
||||
if ($payload === null) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
...$payload,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreCompetitionWinnerRequest $request): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$validated = $request->validated();
|
||||
$id = $this->service->storeCompetition(
|
||||
$validated,
|
||||
(array) ($validated['winner_overrides'] ?? []),
|
||||
(array) ($validated['question_counts'] ?? []),
|
||||
(array) ($validated['prizes'] ?? []),
|
||||
$guard
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Competition created.',
|
||||
'id' => $id,
|
||||
], Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
public function update(UpdateCompetitionWinnerRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$validated = $request->validated();
|
||||
$updated = $this->service->updateCompetition(
|
||||
$id,
|
||||
$validated,
|
||||
(array) ($validated['winner_overrides'] ?? []),
|
||||
(array) ($validated['question_counts'] ?? []),
|
||||
(array) ($validated['prizes'] ?? [])
|
||||
);
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Competition updated.',
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function scores(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$payload = $this->service->editScoresData($id, $this->intOrNull($request->query('class_section_id')));
|
||||
if ($payload === null) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
...$payload,
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveScores(SaveCompetitionScoresRequest $request, int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$competition = $this->service->editScoresData($id, $this->intOrNull($request->input('class_section_id')));
|
||||
if ($competition === null) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$payload = $request->validated();
|
||||
$saved = $this->service->saveScores(
|
||||
$id,
|
||||
(array) ($payload['scores'] ?? []),
|
||||
$this->intOrNull($payload['class_section_id'] ?? null),
|
||||
(array) ($competition['competition'] ?? [])
|
||||
);
|
||||
|
||||
if (! $saved) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Select a class section before saving scores.',
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Scores saved.',
|
||||
'savedCount' => count((array) ($payload['scores'] ?? [])),
|
||||
]);
|
||||
}
|
||||
|
||||
public function preview(int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$payload = $this->service->previewData($id);
|
||||
if ($payload === null) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
...$payload,
|
||||
]);
|
||||
}
|
||||
|
||||
public function winners(int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$payload = $this->service->settingsFormData($id);
|
||||
if ($payload === null || ! isset($payload['competition'])) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'competition' => $payload['competition'],
|
||||
'rows' => $this->service->winnersListingRows($id),
|
||||
'sectionMap' => $this->service->getClassSectionMap(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function publish(int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
if (! $this->service->publishWinners($id)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Competition winners published.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function lock(int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
if (! $this->service->lockCompetition($id, $guard)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Competition locked.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function unlock(int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
if (! $this->service->unlockCompetition($id)) {
|
||||
return response()->json([
|
||||
'ok' => false,
|
||||
'message' => 'Competition not found.',
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'ok' => true,
|
||||
'message' => 'Competition unlocked.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function exportQuiz(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$guard = $this->authenticatedAdminOrForbidden();
|
||||
if ($guard instanceof JsonResponse) {
|
||||
return $guard;
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'class_section_id' => ['nullable', 'integer'],
|
||||
'semester' => ['nullable', 'string'],
|
||||
'school_year' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$schoolId = $this->intOrNull(optional(auth()->user())->school_id);
|
||||
$result = $this->service->exportCompetitionToQuiz(
|
||||
$id,
|
||||
$this->intOrNull($validated['class_section_id'] ?? null) ?? 0,
|
||||
$schoolId,
|
||||
$guard,
|
||||
$validated['semester'] ?? null,
|
||||
$validated['school_year'] ?? null
|
||||
);
|
||||
|
||||
return response()->json($result, $result['ok'] ? Response::HTTP_OK : Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|JsonResponse
|
||||
*/
|
||||
private function authenticatedAdminOrForbidden(): int|JsonResponse
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
if (! $user || (int) ($user->id ?? 0) <= 0) {
|
||||
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], Response::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$roles = array_map('strtolower', (array) ($user->roles ?? []));
|
||||
$allowed = ['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'];
|
||||
if (empty(array_intersect($roles, $allowed))) {
|
||||
return response()->json(['ok' => false, 'message' => 'Forbidden.'], Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
return (int) $user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed> $rows
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
private function iterableToArray(iterable $rows): array
|
||||
{
|
||||
return is_array($rows) ? $rows : iterator_to_array($rows, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizeCompetitionRow(array $row): array
|
||||
{
|
||||
$id = $this->intOrNull($row['id'] ?? $row['competition_id'] ?? null);
|
||||
if ($id !== null) {
|
||||
$row['id'] = $id;
|
||||
}
|
||||
|
||||
$classSectionId = $this->intOrNull($row['class_section_id'] ?? null);
|
||||
$row['class_section_id'] = $classSectionId;
|
||||
$row['is_locked'] = (bool) ($row['is_locked'] ?? false);
|
||||
$row['is_published'] = (bool) ($row['is_published'] ?? false);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $row
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function rowToArray(mixed $row): array
|
||||
{
|
||||
if (is_array($row)) {
|
||||
return $row;
|
||||
}
|
||||
|
||||
if ($row instanceof \JsonSerializable) {
|
||||
$serialized = $row->jsonSerialize();
|
||||
if (is_array($serialized)) {
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($row) && method_exists($row, 'toArray')) {
|
||||
$array = $row->toArray();
|
||||
if (is_array($array)) {
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
return (array) $row;
|
||||
}
|
||||
|
||||
private function intOrNull(mixed $value): ?int
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user