add all controllers logic

This commit is contained in:
root
2026-03-11 17:53:15 -04:00
parent 3e6c577085
commit 2ef71cc92b
421 changed files with 12009 additions and 5211 deletions
@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Api\Attendance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Attendance\LateSlipLogIndexRequest;
use App\Http\Resources\Attendance\LateSlipLogCollection;
use App\Http\Resources\Attendance\LateSlipLogResource;
use App\Models\LateSlipLog;
use App\Services\Attendance\LateSlipLogCommandService;
use App\Services\Attendance\LateSlipLogQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class LateSlipLogsController extends BaseApiController
{
public function __construct(
private LateSlipLogQueryService $queryService,
private LateSlipLogCommandService $commandService
) {
parent::__construct();
}
public function index(LateSlipLogIndexRequest $request): JsonResponse
{
$this->authorize('viewAny', LateSlipLog::class);
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 50);
$logs = $this->queryService->paginate($filters, $page, $perPage);
$collection = new LateSlipLogCollection($logs);
return $this->success([
'logs' => $collection->toArray($request),
'meta' => $collection->with($request)['meta'],
]);
}
public function show(int $id): JsonResponse
{
$log = $this->queryService->find($id);
if (!$log) {
return $this->error('Late slip log not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('view', $log);
return $this->success([
'log' => new LateSlipLogResource($log),
]);
}
public function destroy(int $id): JsonResponse
{
$log = $this->queryService->find($id);
if (!$log) {
return $this->error('Late slip log not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('delete', $log);
try {
$deleted = $this->commandService->delete($log);
} catch (\Throwable $e) {
Log::error('Late slip log delete failed: ' . $e->getMessage());
return $this->error('Unable to delete late slip log.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!$deleted) {
return $this->error('Unable to delete late slip log.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Late slip log deleted.');
}
}
@@ -0,0 +1,115 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Security\IpBanIndexRequest;
use App\Http\Requests\Security\IpBanRequest;
use App\Http\Requests\Security\IpUnbanRequest;
use App\Http\Resources\Security\IpBanCollection;
use App\Http\Resources\Security\IpBanResource;
use App\Models\IpAttempt;
use App\Services\Security\IpBanCommandService;
use App\Services\Security\IpBanQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class IpBanController extends BaseApiController
{
public function __construct(
private IpBanQueryService $queryService,
private IpBanCommandService $commandService
) {
parent::__construct();
}
public function index(IpBanIndexRequest $request): JsonResponse
{
$this->authorize('viewAny', IpAttempt::class);
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$bans = $this->queryService->paginate($filters, $page, $perPage);
$collection = new IpBanCollection($bans);
return $this->success([
'bans' => $collection->toArray($request),
'meta' => $collection->with($request)['meta'],
]);
}
public function show(int $id): JsonResponse
{
$ban = $this->queryService->find($id);
if (!$ban) {
return $this->error('IP record not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('view', $ban);
return $this->success([
'ban' => new IpBanResource($ban),
]);
}
public function ban(IpBanRequest $request): JsonResponse
{
$this->authorize('create', IpAttempt::class);
$validated = $request->validated();
$id = $validated['id'] ?? null;
$ip = $validated['ip'] ?? null;
$hours = (int) ($validated['hours'] ?? 24);
try {
$ban = $this->commandService->banNow($id ? (int) $id : null, $ip ? (string) $ip : null, $hours);
} catch (\Throwable $e) {
Log::error('IP ban failed: ' . $e->getMessage());
return $this->error('Unable to ban IP.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!$ban) {
return $this->error('IP record not found.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'ban' => new IpBanResource($ban),
], 'IP banned.');
}
public function unban(IpUnbanRequest $request): JsonResponse
{
$this->authorize('update', IpAttempt::class);
$validated = $request->validated();
$all = filter_var($validated['all'] ?? false, FILTER_VALIDATE_BOOLEAN);
try {
if ($all) {
$count = $this->commandService->unbanAll();
return $this->success([
'count' => $count,
], $count . ' IP(s) unbanned.');
}
$ban = $this->commandService->unbanOne(
isset($validated['id']) ? (int) $validated['id'] : null,
$validated['ip'] ?? null
);
} catch (\Throwable $e) {
Log::error('IP unban failed: ' . $e->getMessage());
return $this->error('Unable to unban IP.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!$ban) {
return $this->error('IP record not found.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'ban' => new IpBanResource($ban),
], 'IP unbanned.');
}
}
@@ -1,67 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Auth\SessionTimeoutCheckRequest;
use App\Http\Requests\Auth\SessionTimeoutConfigRequest;
use App\Http\Requests\Auth\SessionTimeoutPingRequest;
use App\Http\Resources\Auth\SessionTimeoutConfigResource;
use App\Http\Resources\Auth\SessionTimeoutStatusResource;
use App\Services\Auth\SessionTimeoutConfigService;
use App\Services\Auth\SessionTimeoutService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class SessionTimeoutController extends BaseApiController
{
public function __construct(
private SessionTimeoutConfigService $configService,
private SessionTimeoutService $timeoutService
) {
parent::__construct();
}
public function config(SessionTimeoutConfigRequest $request): JsonResponse
{
$config = $this->configService->config();
return $this->success([
'config' => new SessionTimeoutConfigResource($config),
]);
}
public function check(SessionTimeoutCheckRequest $request): JsonResponse
{
$result = $this->timeoutService->checkTimeout();
if (($result['status'] ?? '') === 'expired') {
return $this->error(
$result['message'] ?? 'Session expired.',
Response::HTTP_UNAUTHORIZED,
(new SessionTimeoutStatusResource($result))->toArray($request)
);
}
return $this->success([
'session' => new SessionTimeoutStatusResource($result),
]);
}
public function ping(SessionTimeoutPingRequest $request): JsonResponse
{
$result = $this->timeoutService->pingActivity();
if (($result['status'] ?? '') === 'expired') {
return $this->error(
$result['message'] ?? 'Session expired.',
Response::HTTP_UNAUTHORIZED,
(new SessionTimeoutStatusResource($result))->toArray($request)
);
}
return $this->success([
'session' => new SessionTimeoutStatusResource($result),
]);
}
}
+15 -18
View File
@@ -229,7 +229,7 @@ class BaseApiController extends Controller
protected function getCurrentUserId(): ?int
{
$userId = (int) (session()->get('user_id') ?? 0);
$userId = (int) (auth()->id() ?? 0);
return $userId > 0 ? $userId : null;
}
@@ -251,23 +251,20 @@ class BaseApiController extends Controller
$name = $row['email'] ?? 'User #' . $userId;
}
$roles = (array) (session()->get('roles') ?? []);
if (empty($roles)) {
try {
$roles = DB::table('user_roles ur')
->select('LOWER(r.name) AS name')
->join('roles r', 'r.id', '=', 'ur.role_id')
->where('ur.user_id', $userId)
->whereNull('ur.deleted_at')
->pluck('name')
->map(fn($name) => strtolower((string) $name))
->unique()
->values()
->toArray();
} catch (\Throwable $e) {
Log::error('Unable to load user roles: ' . $e->getMessage());
$roles = [];
}
try {
$roles = DB::table('user_roles ur')
->select('LOWER(r.name) AS name')
->join('roles r', 'r.id', '=', 'ur.role_id')
->where('ur.user_id', $userId)
->whereNull('ur.deleted_at')
->pluck('name')
->map(fn($name) => strtolower((string) $name))
->unique()
->values()
->toArray();
} catch (\Throwable $e) {
Log::error('Unable to load user roles: ' . $e->getMessage());
$roles = [];
}
return (object) [
@@ -3,9 +3,16 @@
namespace App\Http\Controllers\Api\ClassPreparation;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\ClassPreparation\ClassPreparationAdjustmentRequest;
use App\Http\Requests\ClassPreparation\ClassPreparationIndexRequest;
use App\Http\Requests\ClassPreparation\ClassPreparationMarkPrintedRequest;
use App\Http\Requests\ClassPreparation\ClassPreparationPrintRequest;
use App\Http\Resources\ClassPreparation\ClassPreparationPrintResource;
use App\Http\Resources\ClassPreparation\ClassPreparationResultResource;
use App\Services\ClassPreparation\ClassPreparationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class ClassPreparationController extends BaseApiController
{
@@ -17,7 +24,7 @@ class ClassPreparationController extends BaseApiController
$this->service = $service;
}
public function index(Request $request): JsonResponse
public function index(ClassPreparationIndexRequest $request): JsonResponse
{
$schoolYear = (string) ($request->query('school_year') ?? '');
$semester = (string) ($request->query('semester') ?? '');
@@ -27,34 +34,63 @@ class ClassPreparationController extends BaseApiController
$semester !== '' ? $semester : null
);
return response()->json([
'ok' => true,
return $this->success([
'schoolYear' => $payload['schoolYear'],
'semester' => $payload['semester'],
'results' => $payload['results'],
'results' => ClassPreparationResultResource::collection($payload['results']),
'totals' => $payload['totals'],
'shortages' => $payload['shortages'],
]);
}
public function markPrinted(Request $request): JsonResponse
public function markPrinted(ClassPreparationMarkPrintedRequest $request): JsonResponse
{
$schoolYear = (string) ($request->input('school_year') ?? '');
$semester = (string) ($request->input('semester') ?? '');
$ids = $request->input('class_section_ids', []);
if ($schoolYear === '' || !is_array($ids)) {
return response()->json([
'ok' => false,
'message' => 'school_year and class_section_ids are required.',
], 422);
}
$validated = $request->validated();
$schoolYear = (string) ($validated['school_year'] ?? '');
$semester = (string) ($validated['semester'] ?? '');
$ids = $validated['class_section_ids'] ?? [];
$count = $this->service->markPrinted($schoolYear, $semester, $ids);
return response()->json([
'ok' => true,
return $this->success([
'updated' => $count,
]);
], 'Class preparation logs updated.');
}
public function saveAdjustments(ClassPreparationAdjustmentRequest $request): JsonResponse
{
$validated = $request->validated();
$classSectionId = (string) $validated['class_section_id'];
$schoolYear = (string) ($validated['school_year'] ?? '');
try {
$count = $this->service->saveAdjustments($classSectionId, $schoolYear, $validated['adjustments']);
} catch (\Throwable $e) {
Log::error('Class prep adjustment save failed: ' . $e->getMessage());
return $this->error('Unable to save adjustments.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'updated' => $count,
], 'Adjustments saved.');
}
public function print(ClassPreparationPrintRequest $request): JsonResponse
{
$validated = $request->validated();
$payload = $this->service->printPrep(
(string) $validated['class_section_id'],
(string) $validated['school_year'],
(string) ($validated['semester'] ?? '')
);
if (empty($payload['ok'])) {
return $this->error('Unable to log class preparation print.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'print' => new ClassPreparationPrintResource($payload),
], 'Class preparation logged.');
}
}
@@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Api\Classes;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Classes\ClassSectionIndexRequest;
use App\Http\Requests\Classes\ClassSectionStoreRequest;
use App\Http\Requests\Classes\ClassSectionUpdateRequest;
use App\Http\Resources\Classes\ClassAttendanceResource;
use App\Http\Resources\Classes\ClassSectionCollection;
use App\Http\Resources\Classes\ClassSectionResource;
use App\Models\ClassSection;
use App\Services\ClassSections\ClassAttendanceService;
use App\Services\ClassSections\ClassSectionCommandService;
use App\Services\ClassSections\ClassSectionQueryService;
use App\Services\ClassSections\ClassSectionSeedService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class ClassController extends BaseApiController
{
public function __construct(
private ClassSectionQueryService $queryService,
private ClassSectionCommandService $commandService,
private ClassAttendanceService $attendanceService,
private ClassSectionSeedService $seedService
) {
parent::__construct();
}
public function index(ClassSectionIndexRequest $request): JsonResponse
{
$this->authorize('viewAny', ClassSection::class);
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$sections = $this->queryService->list($filters, $page, $perPage);
$collection = new ClassSectionCollection($sections);
return $this->success([
'sections' => $collection->toArray($request),
'meta' => $collection->with($request)['meta'],
]);
}
public function show(ClassSection $classSection): JsonResponse
{
$this->authorize('view', $classSection);
$section = $this->queryService->find((int) $classSection->id);
if (!$section) {
return $this->error('Class section not found.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'section' => new ClassSectionResource($section),
]);
}
public function store(ClassSectionStoreRequest $request): JsonResponse
{
$this->authorize('create', ClassSection::class);
$section = $this->commandService->create($request->validated());
return $this->success([
'section' => new ClassSectionResource($section),
], 'Class section created.', Response::HTTP_CREATED);
}
public function update(ClassSectionUpdateRequest $request, ClassSection $classSection): JsonResponse
{
$this->authorize('update', $classSection);
$section = $this->commandService->update($classSection, $request->validated());
return $this->success([
'section' => new ClassSectionResource($section),
], 'Class section updated.');
}
public function destroy(ClassSection $classSection): JsonResponse
{
$this->authorize('delete', $classSection);
if (!$this->commandService->delete($classSection)) {
return $this->error('Class section not found.', Response::HTTP_NOT_FOUND);
}
return $this->success(null, 'Class section deleted.');
}
public function attendance(Request $request, int $classSectionId): JsonResponse
{
$teacherId = (int) ($request->user()?->id ?? 0);
if ($teacherId <= 0) {
return $this->error('Missing teacher authentication.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->attendanceService->getAttendancePayload(
$classSectionId,
$teacherId,
$request->query('school_year'),
$request->query('semester')
);
if (empty($payload['students'])) {
return $this->error('No data found for this class.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'attendance' => new ClassAttendanceResource($payload),
]);
}
public function seedDefaults(): JsonResponse
{
$this->authorize('seedDefaults', ClassSection::class);
try {
$created = $this->seedService->seedDefaults();
} catch (\Throwable $e) {
Log::error('Seeding default classes failed: ' . $e->getMessage());
return $this->error('Unable to seed default classes.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'created' => $created,
], 'Default classes seeded.');
}
}
@@ -34,7 +34,8 @@ class PaypalPaymentController extends BaseApiController
$payload = $request->validated();
$this->service->executePayment(
(string) $payload['payer_id'],
$payload['paypal_payment_id'] ?? null
$payload['paypal_payment_id'] ?? null,
isset($payload['payment_id']) ? (int) $payload['payment_id'] : null
);
return response()->json(['ok' => true]);
@@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Resources\Frontend\FrontendPageResource;
use App\Http\Resources\Frontend\FrontendUserResource;
use App\Services\Frontend\FrontendPageService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class FrontendController extends BaseApiController
{
public function __construct(private FrontendPageService $pageService)
{
parent::__construct();
}
public function index(): JsonResponse
{
return $this->success([
'page' => new FrontendPageResource($this->pageService->page('index')),
]);
}
public function facility(): JsonResponse
{
return $this->success([
'page' => new FrontendPageResource($this->pageService->page('facility')),
]);
}
public function team(): JsonResponse
{
return $this->success([
'page' => new FrontendPageResource($this->pageService->page('team')),
]);
}
public function callToAction(): JsonResponse
{
return $this->success([
'page' => new FrontendPageResource($this->pageService->page('call_to_action')),
]);
}
public function testimonial(): JsonResponse
{
return $this->success([
'page' => new FrontendPageResource($this->pageService->page('testimonial')),
]);
}
public function notFound(): JsonResponse
{
return $this->success([
'page' => new FrontendPageResource($this->pageService->page('not_found')),
]);
}
public function fetchUser(): JsonResponse
{
$user = auth()->user();
if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $this->success([
'user' => new FrontendUserResource($user),
]);
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Resources\Frontend\ProfileIconResource;
use App\Services\Frontend\ProfileIconService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class InfoIconController extends BaseApiController
{
public function __construct(private ProfileIconService $service)
{
parent::__construct();
}
public function profileIcon(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->service->buildForUser($userId);
return $this->success([
'profile' => new ProfileIconResource($payload),
]);
}
}
@@ -0,0 +1,86 @@
<?php
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Frontend\LandingTeacherRequest;
use App\Http\Resources\Frontend\LandingPageResource;
use App\Services\Frontend\LandingPageService;
use Illuminate\Http\JsonResponse;
class LandingPageController extends BaseApiController
{
public function __construct(private LandingPageService $service)
{
parent::__construct();
}
public function index(LandingTeacherRequest $request): JsonResponse
{
$payload = $this->service->dashboardForUser(
$request->user(),
$request->validated()['class_section_id'] ?? null
);
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
public function teacher(LandingTeacherRequest $request): JsonResponse
{
$payload = $this->service->teacher(
$request->user(),
$request->validated()['class_section_id'] ?? null
);
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
public function parent(): JsonResponse
{
$payload = $this->service->parent(auth()->user());
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
public function administrator(): JsonResponse
{
$payload = $this->service->administrator();
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
public function admin(): JsonResponse
{
$payload = $this->service->admin();
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
public function student(): JsonResponse
{
$payload = $this->service->student();
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
public function guest(): JsonResponse
{
$payload = $this->service->guest();
return $this->success([
'landing' => new LandingPageResource($payload),
]);
}
}
@@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Frontend\ContactSubmitRequest;
use App\Http\Resources\Frontend\ContactSubmissionResource;
use App\Http\Resources\Frontend\PageContentResource;
use App\Services\Frontend\ContactSubmissionService;
use App\Services\Frontend\StaticPageService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class PageController extends BaseApiController
{
public function __construct(
private StaticPageService $pageService,
private ContactSubmissionService $contactService
) {
parent::__construct();
}
public function privacyPolicy(): JsonResponse
{
return $this->renderStatic('privacy_policy.html', 'privacy_policy');
}
public function termsOfService(): JsonResponse
{
return $this->renderStatic('terms_of_service.html', 'terms_of_service');
}
public function helpCenter(): JsonResponse
{
return $this->renderStatic('help_center.html', 'help_center');
}
public function submitContact(ContactSubmitRequest $request): JsonResponse
{
$result = $this->contactService->submit($request->validated());
return $this->success([
'submission' => new ContactSubmissionResource([
'email' => $request->validated('email'),
'email_sent' => $result['email_sent'],
'contact_id' => $result['record']?->id,
]),
], 'Message received.', Response::HTTP_CREATED);
}
private function renderStatic(string $filename, string $type): JsonResponse
{
$result = $this->pageService->load($filename, $type);
if (!$result['ok']) {
return $this->error($result['error'], Response::HTTP_NOT_FOUND);
}
return $this->success([
'page' => new PageContentResource($result),
]);
}
}
@@ -0,0 +1,222 @@
<?php
namespace App\Http\Controllers\Api\Messaging;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Messaging\MessageIndexRequest;
use App\Http\Requests\Messaging\MessageSendRequest;
use App\Http\Requests\Messaging\MessageUpdateRequest;
use App\Http\Resources\Messaging\MessageCollection;
use App\Http\Resources\Messaging\MessageResource;
use App\Http\Resources\Messaging\RecipientResource;
use App\Models\Message;
use App\Services\Messaging\MessageCommandService;
use App\Services\Messaging\MessageQueryService;
use App\Services\Messaging\MessageRecipientService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class MessagesController extends BaseApiController
{
public function __construct(
private MessageQueryService $queryService,
private MessageCommandService $commandService,
private MessageRecipientService $recipientService
) {
parent::__construct();
}
public function index(MessageIndexRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$this->authorize('viewAny', Message::class);
$role = $this->queryService->getUserRoleName($userId);
$messages = $this->queryService->inbox($userId, $filters, $page, $perPage);
$collection = new MessageCollection($messages);
$meta = $collection->with($request)['meta'] ?? null;
return $this->success([
'role' => $role,
'messages' => $collection->toArray($request),
'meta' => $meta,
]);
}
public function inbox(MessageIndexRequest $request): JsonResponse
{
return $this->listMessages($request, 'inbox');
}
public function sent(MessageIndexRequest $request): JsonResponse
{
return $this->listMessages($request, 'sent');
}
public function drafts(MessageIndexRequest $request): JsonResponse
{
return $this->listMessages($request, 'drafts');
}
public function trash(MessageIndexRequest $request): JsonResponse
{
return $this->listMessages($request, 'trash');
}
public function show(int $id): JsonResponse
{
$message = $this->queryService->findForUser($id);
if (!$message) {
return $this->error('Message not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('view', $message);
$userId = (int) (auth()->id() ?? 0);
if ($message->recipient_id === $userId) {
$this->commandService->markRead($message);
$message->refresh();
}
return $this->success([
'message' => new MessageResource($message),
]);
}
public function store(MessageSendRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$this->authorize('create', Message::class);
try {
$message = $this->commandService->create($userId, $request->validated(), $request->file('attachment'));
} catch (\Throwable $e) {
Log::error('Message send failed: ' . $e->getMessage());
return $this->error($e->getMessage(), Response::HTTP_BAD_REQUEST);
}
return $this->success([
'message' => new MessageResource($message->fresh(['sender', 'recipient'])),
], 'Message sent.', Response::HTTP_CREATED);
}
public function receive(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$messages = $this->queryService->receivedAndMarkRead($userId);
return $this->success([
'messages' => (new MessageCollection(collect($messages)))->toArray(request()),
]);
}
public function recipients(string $type): JsonResponse
{
$type = strtolower(trim($type));
$data = match ($type) {
'teacher' => $this->recipientService->teachers(),
'parent' => $this->recipientService->parents(),
default => null,
};
if ($data === null) {
return $this->error('Invalid recipient type.', Response::HTTP_BAD_REQUEST);
}
$payload = array_map(fn ($row) => (new RecipientResource($row))->toArray(request()), $data);
return $this->success([
'recipients' => $payload,
]);
}
public function update(MessageUpdateRequest $request, int $id): JsonResponse
{
$message = Message::query()->find($id);
if (!$message) {
return $this->error('Message not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('update', $message);
try {
$updated = $this->commandService->update($message, $request->validated());
} catch (\Throwable $e) {
Log::error('Message update failed: ' . $e->getMessage());
return $this->error('Unable to update message.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'message' => new MessageResource($updated->load(['sender', 'recipient'])),
], 'Message updated.');
}
public function destroy(int $id): JsonResponse
{
$message = Message::query()->find($id);
if (!$message) {
return $this->error('Message not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('delete', $message);
try {
$trashed = $this->commandService->trash($message);
} catch (\Throwable $e) {
Log::error('Message delete failed: ' . $e->getMessage());
return $this->error('Unable to delete message.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!$trashed) {
return $this->error('Unable to delete message.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Message deleted.');
}
private function listMessages(MessageIndexRequest $request, string $bucket): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$this->authorize('viewAny', Message::class);
$paginator = match ($bucket) {
'sent' => $this->queryService->sent($userId, $filters, $page, $perPage),
'drafts' => $this->queryService->drafts($userId, $filters, $page, $perPage),
'trash' => $this->queryService->trash($userId, $filters, $page, $perPage),
default => $this->queryService->inbox($userId, $filters, $page, $perPage),
};
$collection = new MessageCollection($paginator);
$meta = $collection->with($request)['meta'] ?? null;
return $this->success([
'messages' => $collection->toArray($request),
'meta' => $meta,
]);
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Settings\PolicyShowRequest;
use App\Http\Resources\Settings\PolicyResource;
use App\Services\Policy\PolicyContentService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class PolicyController extends BaseApiController
{
public function __construct(private PolicyContentService $service)
{
parent::__construct();
}
public function school(PolicyShowRequest $request): JsonResponse
{
return $this->respondPolicy('school');
}
public function picture(PolicyShowRequest $request): JsonResponse
{
return $this->respondPolicy('picture');
}
private function respondPolicy(string $type): JsonResponse
{
try {
$policy = $this->service->getPolicy($type);
} catch (\InvalidArgumentException $e) {
return $this->error('Policy not found.', Response::HTTP_NOT_FOUND);
} catch (\Throwable $e) {
return $this->error('Unable to load policy.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'policy' => new PolicyResource($policy),
]);
}
}
@@ -0,0 +1,139 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Preferences\PreferencesIndexRequest;
use App\Http\Requests\Preferences\PreferencesUpsertRequest;
use App\Http\Resources\Preferences\PreferencesCollection;
use App\Http\Resources\Preferences\PreferencesResource;
use App\Models\Preferences;
use App\Services\Preferences\PreferencesCommandService;
use App\Services\Preferences\PreferencesQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class PreferencesController extends BaseApiController
{
public function __construct(
private PreferencesQueryService $queryService,
private PreferencesCommandService $commandService
) {
parent::__construct();
}
public function index(PreferencesIndexRequest $request): JsonResponse
{
$this->authorize('viewAny', Preferences::class);
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$rows = $this->queryService->paginate($filters, $page, $perPage);
$collection = new PreferencesCollection($rows);
return $this->success([
'preferences' => $collection->toArray($request),
'meta' => $collection->with($request)['meta'],
]);
}
public function show(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->queryService->getForUser($userId);
return $this->success([
'preferences' => new PreferencesResource($payload['preferences']),
'options' => $payload['options'],
]);
}
public function showForUser(int $userId): JsonResponse
{
$row = Preferences::query()->where('user_id', $userId)->first();
if (!$row) {
return $this->error('Preferences not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('view', $row);
$payload = $this->queryService->getForUser($userId);
return $this->success([
'preferences' => new PreferencesResource($payload['preferences']),
'options' => $payload['options'],
]);
}
public function store(PreferencesUpsertRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
try {
$saved = $this->commandService->upsert($userId, $request->validated());
} catch (\Throwable $e) {
Log::error('Preferences save failed: ' . $e->getMessage());
return $this->error('Unable to save preferences.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
$payload = $this->queryService->getForUser($userId);
return $this->success([
'preferences' => new PreferencesResource($payload['preferences']),
], 'Preferences saved.', $saved->wasRecentlyCreated ? Response::HTTP_CREATED : Response::HTTP_OK);
}
public function updateForUser(PreferencesUpsertRequest $request, int $userId): JsonResponse
{
$row = Preferences::query()->where('user_id', $userId)->first();
if ($row) {
$this->authorize('update', $row);
}
try {
$saved = $this->commandService->upsert($userId, $request->validated());
} catch (\Throwable $e) {
Log::error('Preferences update failed: ' . $e->getMessage());
return $this->error('Unable to save preferences.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
$payload = $this->queryService->getForUser($userId);
return $this->success([
'preferences' => new PreferencesResource($payload['preferences']),
], 'Preferences saved.', $saved->wasRecentlyCreated ? Response::HTTP_CREATED : Response::HTTP_OK);
}
public function destroy(int $userId): JsonResponse
{
$row = Preferences::query()->where('user_id', $userId)->first();
if (!$row) {
return $this->error('Preferences not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('delete', $row);
try {
$deleted = $this->commandService->delete($row);
} catch (\Throwable $e) {
Log::error('Preferences delete failed: ' . $e->getMessage());
return $this->error('Unable to delete preferences.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!$deleted) {
return $this->error('Unable to delete preferences.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Preferences deleted.');
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Settings\SettingsUpdateRequest;
use App\Http\Resources\Settings\SettingsResource;
use App\Models\Setting;
use App\Services\Settings\SettingsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class SettingsController extends BaseApiController
{
public function __construct(private SettingsService $settingsService)
{
parent::__construct();
}
public function index(): JsonResponse
{
$this->authorize('viewAny', Setting::class);
return $this->success([
'settings' => new SettingsResource($this->settingsService->get()),
]);
}
public function update(SettingsUpdateRequest $request): JsonResponse
{
$settings = Setting::singleton();
$this->authorize('update', $settings);
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
try {
$updated = $this->settingsService->update($request->validated(), $userId);
} catch (\Throwable $e) {
Log::error('Settings update failed: ' . $e->getMessage());
return $this->error('Unable to update settings.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'settings' => new SettingsResource($updated),
], 'Settings updated.');
}
}
@@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Api\Staff;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Staff\StaffIndexRequest;
use App\Http\Requests\Staff\StaffStoreRequest;
use App\Http\Requests\Staff\StaffUpdateRequest;
use App\Http\Resources\Staff\StaffCollection;
use App\Http\Resources\Staff\StaffResource;
use App\Models\Staff;
use App\Services\Staff\StaffCommandService;
use App\Services\Staff\StaffQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class StaffController extends BaseApiController
{
public function __construct(
private StaffQueryService $queryService,
private StaffCommandService $commandService
) {
parent::__construct();
}
public function index(StaffIndexRequest $request): JsonResponse
{
$this->authorize('viewAny', Staff::class);
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$result = $this->queryService->paginate($filters, $page, $perPage);
$collection = new StaffCollection($result['paginator']);
return $this->success([
'staff' => $collection->toArray($request),
'meta' => $collection->with($request)['meta'] ?? null,
'issues_count' => $result['issues_count'],
'semester' => $result['semester'],
'school_year' => $result['school_year'],
]);
}
public function show(int $id): JsonResponse
{
$staff = $this->queryService->find($id);
if (!$staff) {
return $this->error('Staff member not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('view', $staff);
$staff->school_id = \App\Models\User::getSchoolIdByUserId((int) ($staff->user_id ?? 0));
return $this->success([
'staff' => new StaffResource($staff),
]);
}
public function store(StaffStoreRequest $request): JsonResponse
{
$this->authorize('create', Staff::class);
try {
$staff = $this->commandService->create($request->validated());
} catch (\Throwable $e) {
Log::error('Staff create failed: ' . $e->getMessage());
return $this->error($e->getMessage(), Response::HTTP_BAD_REQUEST);
}
return $this->success([
'staff' => new StaffResource($staff),
], 'Staff member added.', Response::HTTP_CREATED);
}
public function update(StaffUpdateRequest $request, int $id): JsonResponse
{
$staff = $this->queryService->find($id);
if (!$staff) {
return $this->error('Staff member not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('update', $staff);
try {
$updated = $this->commandService->update($staff, $request->validated());
} catch (\Throwable $e) {
Log::error('Staff update failed: ' . $e->getMessage());
return $this->error('Unable to update staff.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'staff' => new StaffResource($updated),
], 'Staff member updated.');
}
public function destroy(int $id): JsonResponse
{
$staff = $this->queryService->find($id);
if (!$staff) {
return $this->error('Staff member not found.', Response::HTTP_NOT_FOUND);
}
$this->authorize('delete', $staff);
try {
$deleted = $this->commandService->delete($staff);
} catch (\Throwable $e) {
Log::error('Staff delete failed: ' . $e->getMessage());
return $this->error('Unable to delete staff.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!$deleted) {
return $this->error('Unable to delete staff.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Staff member deleted.');
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Api\Support;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Support\ContactSendRequest;
use App\Http\Resources\Support\ContactMessageResource;
use App\Services\Support\ContactMessageService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class ContactController extends BaseApiController
{
public function __construct(private ContactMessageService $messageService)
{
parent::__construct();
}
public function send(ContactSendRequest $request): JsonResponse
{
$result = $this->messageService->send($request->validated());
return $this->success([
'contact' => new ContactMessageResource($result),
], 'Message sent.', Response::HTTP_CREATED);
}
}
@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api\Support;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Support\SupportRequestIndexRequest;
use App\Http\Requests\Support\SupportRequestStoreRequest;
use App\Http\Resources\Support\SupportRequestCollection;
use App\Http\Resources\Support\SupportRequestResource;
use App\Models\SupportRequest;
use App\Services\Support\SupportRequestService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class SupportController extends BaseApiController
{
public function __construct(private SupportRequestService $supportService)
{
parent::__construct();
}
public function index(SupportRequestIndexRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$this->authorize('viewAny', SupportRequest::class);
$filters = $request->validated();
$page = (int) ($filters['page'] ?? 1);
$perPage = (int) ($filters['per_page'] ?? 20);
$isAdmin = auth()->user()?->roles()
->pluck('roles.name')
->map(fn ($name) => strtolower((string) $name))
->intersect(['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'])
->isNotEmpty();
$paginator = $this->supportService->list($userId, $filters, $page, $perPage, $isAdmin);
$collection = new SupportRequestCollection($paginator);
return $this->success([
'requests' => $collection->toArray($request),
'meta' => $collection->with($request)['meta'] ?? null,
]);
}
public function store(SupportRequestStoreRequest $request): JsonResponse
{
$user = auth()->user();
if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$this->authorize('create', SupportRequest::class);
$payload = $request->validated();
$payload['user_email'] = $user->email ?? null;
$payload['user_name'] = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? ''));
try {
$result = $this->supportService->create($user->id, $payload);
} catch (\Throwable $e) {
Log::error('Support request failed: ' . $e->getMessage());
return $this->error('Unable to submit support request.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'request' => new SupportRequestResource($result['record']),
'email_sent' => $result['email_sent'],
], 'Support request submitted.', Response::HTTP_CREATED);
}
public function teacher(): JsonResponse
{
return $this->success([
'page' => 'teacher_support',
]);
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Resources\System\DashboardRouteResource;
use App\Services\Dashboard\DashboardRouteService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class DashboardController extends BaseApiController
{
public function __construct(private DashboardRouteService $service)
{
parent::__construct();
}
public function route(): JsonResponse
{
$user = auth()->user();
if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->service->resolveForUser($user);
return $this->success([
'dashboard' => new DashboardRouteResource($payload),
]);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Services\System\DatabaseHealthService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class DatabaseHealthController extends BaseApiController
{
public function __construct(private DatabaseHealthService $healthService)
{
parent::__construct();
}
public function index(): JsonResponse
{
$result = $this->healthService->check();
if (!$result['ok']) {
return $this->error('Database connection failed.', Response::HTTP_SERVICE_UNAVAILABLE, [
'error' => $result['error'],
]);
}
return $this->success([
'ok' => true,
]);
}
}
@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Resources\System\HealthStatusResource;
use App\Services\System\HealthCheckService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class HealthController extends BaseApiController
{
public function __construct(private HealthCheckService $service)
{
parent::__construct();
}
public function index(): JsonResponse
{
$payload = $this->service->check();
$status = $payload['ok'] ? Response::HTTP_OK : Response::HTTP_SERVICE_UNAVAILABLE;
return $this->success([
'health' => new HealthStatusResource($payload),
], 'Health check.', $status);
}
}
@@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\System\NavItemReorderRequest;
use App\Http\Requests\System\NavItemStoreRequest;
use App\Http\Resources\System\NavBuilderDataResource;
use App\Http\Resources\System\NavItemResource;
use App\Models\NavItem;
use App\Services\Navigation\NavBuilderService;
use App\Services\Navigation\NavbarService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class NavBuilderController extends BaseApiController
{
public function __construct(
private NavBuilderService $builderService,
private NavbarService $navbarService
) {
parent::__construct();
}
public function menu(): JsonResponse
{
$user = auth()->user();
$roles = $user?->roles()->pluck('roles.name')->map(fn ($name) => strtolower((string) $name))->toArray() ?? [];
$menu = $this->navbarService->getMenuForRoles($roles);
return $this->success([
'items' => NavItemResource::collection($menu),
]);
}
public function data(): JsonResponse
{
$this->authorize('manage', NavItem::class);
$payload = $this->builderService->getAdminPayload();
return $this->success([
'data' => new NavBuilderDataResource($payload),
]);
}
public function store(NavItemStoreRequest $request): JsonResponse
{
$this->authorize('manage', NavItem::class);
$result = $this->builderService->save($request->validated());
if (empty($result['ok'])) {
return $this->error($result['message'] ?? 'Unable to save menu item.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
return $this->success([
'id' => $result['id'],
], 'Menu saved.', Response::HTTP_CREATED);
}
public function destroy(int $id): JsonResponse
{
$this->authorize('manage', NavItem::class);
$deleted = $this->builderService->delete($id);
if (!$deleted) {
return $this->error('Menu item not found.', Response::HTTP_NOT_FOUND);
}
return $this->success(null, 'Menu item deleted.');
}
public function reorder(NavItemReorderRequest $request): JsonResponse
{
$this->authorize('manage', NavItem::class);
try {
$this->builderService->reorder($request->validated()['orders']);
} catch (\Throwable $e) {
Log::error('Nav reorder failed: ' . $e->getMessage());
return $this->error('Unable to reorder items.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(['ok' => true], 'Menu reordered.');
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\Ui;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Ui\UiStyleRequest;
use App\Http\Resources\Ui\UiStyleResource;
use App\Services\Ui\UiStyleService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class UiController extends BaseApiController
{
public function __construct(private UiStyleService $styleService)
{
parent::__construct();
}
public function style(UiStyleRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$prefs = $this->styleService->update($userId, $request->validated());
return $this->success([
'preferences' => new UiStyleResource($prefs),
], 'Style updated.');
}
}
@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Api\Utilities;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Requests\Utilities\PhoneFormatRequest;
use App\Http\Resources\Utilities\PhoneFormatResource;
use App\Services\Phone\PhoneFormatterService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class PhoneFormatterController extends BaseApiController
{
public function __construct(private PhoneFormatterService $service)
{
parent::__construct();
}
public function format(PhoneFormatRequest $request): JsonResponse
{
$raw = (string) $request->validated()['number'];
$formatted = $this->service->format($raw);
if ($formatted === null) {
return $this->error('Invalid phone number.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
return $this->success([
'phone' => new PhoneFormatResource([
'raw' => $raw,
'formatted' => $formatted,
'is_valid' => true,
]),
]);
}
}