update controllers logic

This commit is contained in:
root
2026-04-23 00:04:35 -04:00
parent 1977a513df
commit ca4ba272fc
353 changed files with 13402 additions and 1301 deletions
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Administrator;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Teachers\TeacherAssignmentDeleteRequest;
use App\Http\Requests\Teachers\TeacherAssignmentListRequest;
use App\Http\Requests\Teachers\TeacherAssignmentStoreRequest;
@@ -32,8 +32,13 @@ class TeacherClassAssignmentController extends BaseApiController
public function store(TeacherAssignmentStoreRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$payload['updated_by'] = (int) (auth()->id() ?? 0);
$payload['updated_by'] = $userId;
$result = $this->assignmentService->assign($payload);
$status = $result['ok'] ? 200 : 422;
@@ -55,4 +60,17 @@ class TeacherClassAssignmentController extends BaseApiController
'message' => $result['message'] ?? '',
], $status);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -46,9 +46,14 @@ class AssignmentApiController extends Controller
], 422);
}
$userId = (int) ($request->user()?->id ?? 0);
if ($userId <= 0) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
$assignment = $this->assignmentService->storeAssignment(
data: $validator->validated(),
updatedBy: $request->user()?->id
updatedBy: $userId
);
return response()->json([
@@ -31,9 +31,14 @@ class AdminAttendanceApiController extends Controller
public function updateManagement(UpdateAttendanceManagementRequest $request): JsonResponse
{
$user = auth()->user();
if ($user === null) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
try {
return response()->json(
$this->attendanceService->updateAttendanceManagement(auth()->user(), $request->validated())
$this->attendanceService->updateAttendanceManagement($user, $request->validated())
);
} catch (Throwable $e) {
return response()->json(['message' => $e->getMessage()], 422);
@@ -42,9 +47,14 @@ class AdminAttendanceApiController extends Controller
public function addEntry(AdminAddAttendanceEntryRequest $request): JsonResponse
{
$user = auth()->user();
if ($user === null) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
try {
return response()->json(
$this->attendanceService->adminAddEntry(auth()->user(), $request->validated())
$this->attendanceService->adminAddEntry($user, $request->validated())
);
} catch (Throwable $e) {
return response()->json(['message' => $e->getMessage()], 422);
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api\Attendance;
use App\Http\Controllers\Controller;
use App\Services\ApplicationUrlService;
use App\Services\AttendanceCommentTemplateService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -11,10 +12,22 @@ use Illuminate\Support\Facades\Validator;
class AttendanceCommentTemplateController extends Controller
{
public function __construct(
protected AttendanceCommentTemplateService $service
protected AttendanceCommentTemplateService $service,
protected ApplicationUrlService $urls,
) {
}
/**
* Bootstrap URLs for admin UIs (legacy CodeIgniter parity + SPA).
*/
public function bootstrapUrls(): JsonResponse
{
return response()->json([
'status' => true,
'data' => $this->urls->attendanceCommentTemplateBootstrapData(),
]);
}
public function index(Request $request): JsonResponse
{
$activeOnly = filter_var(
@@ -119,4 +132,83 @@ class AttendanceCommentTemplateController extends Controller
'message' => 'Template deleted successfully.',
]);
}
/**
* CodeIgniter {@see \App\Controllers\View\AttendanceCommentTemplateController::save}
* POST fields: id?, min_score, max_score, template_text, is_active (checkbox "on").
*/
public function legacySave(Request $request): JsonResponse
{
$id = $request->input('id');
$hasId = $id !== null && $id !== '' && (int) $id > 0;
$rules = [
'min_score' => ['required', 'integer', 'min:0', 'max:100'],
'max_score' => ['required', 'integer', 'min:0', 'max:100'],
'template_text' => ['required', 'string', 'max:5000'],
];
$validator = Validator::make($request->all(), $rules);
$validator->after(function ($v) use ($request) {
$min = (int) $request->input('min_score');
$max = (int) $request->input('max_score');
if ($max < $min) {
$v->errors()->add('max_score', 'The max_score field must be greater than or equal to min_score.');
}
});
if ($validator->fails()) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
}
$isActive = $this->legacyIsActive($request);
$payload = [
'min_score' => (int) $request->input('min_score'),
'max_score' => (int) $request->input('max_score'),
'template_text' => (string) $request->input('template_text'),
'is_active' => $isActive,
];
if ($hasId) {
$this->service->update((int) $id, $payload);
} else {
$this->service->create($payload);
}
return response()->json(['status' => 'success']);
}
/**
* CodeIgniter {@see \App\Controllers\View\AttendanceCommentTemplateController::delete}.
*/
public function legacyDelete(Request $request): JsonResponse
{
$id = $request->input('id');
if ($id === null || $id === '' || (int) $id <= 0) {
return response()->json([
'status' => 'error',
'message' => 'ID not provided',
], 400);
}
$this->service->delete((int) $id);
return response()->json(['status' => 'success']);
}
private function legacyIsActive(Request $request): bool
{
$raw = $request->input('is_active');
return $raw === 'on'
|| $raw === 1
|| $raw === '1'
|| $raw === true
|| $raw === 'true';
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Attendance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Attendance\LateSlipLogIndexRequest;
use App\Http\Resources\Attendance\LateSlipLogCollection;
use App\Http\Resources\Attendance\LateSlipLogResource;
@@ -42,9 +42,14 @@ class StaffAttendanceApiController extends Controller
public function saveAdmins(SaveAdminAttendanceRequest $request): JsonResponse
{
$user = auth()->user();
if ($user === null) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
try {
return response()->json(
$this->staffAttendanceService->saveAdmins(auth()->user(), $request->validated())
$this->staffAttendanceService->saveAdmins($user, $request->validated())
);
} catch (Throwable $e) {
return response()->json(['message' => $e->getMessage()], 422);
@@ -53,9 +58,14 @@ class StaffAttendanceApiController extends Controller
public function saveCell(SaveStaffAttendanceCellRequest $request): JsonResponse
{
$user = auth()->user();
if ($user === null) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
try {
return response()->json(
$this->staffAttendanceService->saveCell(auth()->user(), $request->validated())
$this->staffAttendanceService->saveCell($user, $request->validated())
);
} catch (Throwable $e) {
return response()->json(['message' => $e->getMessage()], 422);
@@ -33,10 +33,15 @@ class TeacherAttendanceApiController extends Controller
public function form(Request $request): JsonResponse|TeacherAttendanceFormResource
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
return new TeacherAttendanceFormResource(
$this->queryService->teacherAttendanceFormData(
auth()->id(),
$guard,
(int)$request->query('class_section_id', 0)
)
);
@@ -47,12 +52,30 @@ class TeacherAttendanceApiController extends Controller
public function submit(TeacherSubmitAttendanceRequest $request): JsonResponse
{
$user = auth()->user();
if ($user === null) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
try {
return response()->json(
$this->attendanceService->submitTeacherAttendance(auth()->user(), $request->validated())
$this->attendanceService->submitTeacherAttendance($user, $request->validated())
);
} catch (Throwable $e) {
return response()->json(['message' => $e->getMessage()], 422);
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,69 +2,83 @@
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Models\User;
use App\Services\Auth\ApiLoginSecurityService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
class AuthController extends BaseApiController
{
public function login(Request $request): JsonResponse
public function login(Request $request, ApiLoginSecurityService $security): JsonResponse
{
$validator = Validator::make($request->all(), [
'email' => ['required', 'email'],
'password' => ['required', 'string'],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
$payload = $request->all();
if ($request->isJson() && ($payload === [] || $payload === null)) {
$decoded = json_decode((string) $request->getContent(), true);
$payload = is_array($decoded) ? $decoded : [];
}
$email = (string) $request->input('email');
$password = (string) $request->input('password');
// Match CodeIgniter apiLogin: raw email/password (no trim); empty check is identical to CI `!$email || !$password`.
$email = (string) ($payload['email'] ?? '');
$password = (string) ($payload['password'] ?? '');
if ($email === '' || $password === '') {
return response()->json([
'status' => false,
'message' => 'Email and password are required.',
], 400);
}
$ip = $request->ip();
if ($security->isIpBlocked((string) $ip)) {
return response()->json([
'status' => false,
'message' => 'Too many failed attempts from your IP. Please try again later.',
], 429);
}
$user = User::query()->where('email', $email)->first();
if (!$user || !ci_password_verify($password, (string) $user->password)) {
return $this->respondError('Invalid credentials.', 401);
if (!$user) {
$security->logIpAttempt((string) $ip);
return response()->json([
'status' => false,
'message' => 'Invalid email or password.',
], 401);
}
if ((int) ($user->is_verified ?? 0) !== 1) {
return $this->respondError('Account not verified.', 403);
if ($user->is_suspended) {
return response()->json([
'status' => false,
'message' => 'Account suspended. Please reset your password.',
], 403);
}
if (strcasecmp((string) ($user->status ?? ''), 'Active') !== 0) {
return $this->respondError('Account is inactive.', 403);
if (! ci_password_verify($password, (string) $user->password)) {
$security->handleFailedLogin($user, $email, (string) $ip);
return response()->json([
'status' => false,
'message' => 'Invalid email or password.',
], 401);
}
$jwtToken = JWTAuth::fromUser($user);
$sanctumToken = $user->createToken('api')->plainTextToken;
$roles = $user->roles()
->where('roles.is_active', 1)
->pluck('roles.name')
->values()
->all();
$security->resetFailedAttempts($user);
$security->logSuccessfulLogin($user->fresh(), $request);
return $this->respondSuccess([
'user' => [
'id' => (int) $user->id,
'firstname' => $user->firstname,
'lastname' => $user->lastname,
'email' => $user->email,
'roles' => $roles,
],
'jwt' => [
'access_token' => $jwtToken,
'token_type' => 'bearer',
'expires_in' => (int) config('jwt.ttl') * 60,
],
'sanctum' => [
'access_token' => $sanctumToken,
'token_type' => 'bearer',
],
], 'Authenticated.');
$fresh = $user->fresh();
if (!$fresh) {
return response()->json([
'status' => false,
'message' => 'Invalid email or password.',
], 401);
}
$jwtToken = JWTAuth::fromUser($fresh);
return response()->json($security->buildLoginResponse($fresh, $jwtToken));
}
public function refresh(Request $request): JsonResponse
@@ -0,0 +1,257 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginSessionRequest;
use App\Http\Requests\Auth\SetRoleSessionRequest;
use App\Models\User;
use App\Services\ApplicationUrlService;
use App\Services\Auth\ApiLoginSecurityService;
use App\Services\Auth\AuthSessionService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Session (cookie) endpoints for SPA — JSON alongside JWT on /api/login.
*
* Routes: GET login, POST user/login, GET logout, GET select-role, POST set-role (registered on web middleware).
*/
class AuthSessionController extends Controller
{
public function __construct(
private ApiLoginSecurityService $security,
private AuthSessionService $sessionAuth,
private ApplicationUrlService $urls,
) {}
/**
* CI `loginMask` — login page bootstrap / redirect when already authenticated.
*/
public function loginMask(Request $request): JsonResponse
{
$redirectTo = $this->sessionAuth->sanitizeRedirectTarget(
(string) ($request->query('redirect_to') ?? ''),
);
if (! Auth::guard('web')->check()) {
return response()->json([
'status' => true,
'authenticated' => false,
'redirect_to' => $redirectTo,
]);
}
/** @var list<string>|null $roles */
$roles = session('roles');
$roles = is_array($roles) ? array_values(array_filter(array_map('strval', $roles))) : [];
$currentRole = session('role');
if ($roles !== [] && count($roles) > 1 && ! $currentRole) {
return response()->json([
'status' => true,
'authenticated' => true,
'next_url' => $this->urls->webSelectRoleUrl(false),
'roles' => $roles,
'pending_redirect' => session('post_login_redirect'),
]);
}
if ($redirectTo !== null) {
return response()->json([
'status' => true,
'authenticated' => true,
'next_url' => $redirectTo,
]);
}
$pick = $currentRole !== null && $currentRole !== ''
? [(string) $currentRole]
: $roles;
return response()->json([
'status' => true,
'authenticated' => true,
'next_url' => $this->sessionAuth->dashboardRouteForRoles($pick),
]);
}
/**
* CI `login` — POST /user/login.
*/
public function login(LoginSessionRequest $request): JsonResponse
{
$email = (string) $request->validated('email');
$password = (string) $request->validated('password');
$redirectRaw = $request->validated('redirect_to');
$sanitizedRedirect = $redirectRaw !== null
? $this->sessionAuth->sanitizeRedirectTarget((string) $redirectRaw)
: null;
$ip = (string) $request->ip();
if ($this->security->isIpBlocked($ip)) {
return response()->json([
'status' => false,
'message' => 'Too many failed attempts from your IP. Please try again later.',
], 429);
}
$user = User::query()->where('email', $email)->first();
if (! $user) {
$this->security->logIpAttempt($ip);
return response()->json([
'status' => false,
'message' => 'The email and password combination you entered is invalid. Please try again.',
], 401);
}
if ($user->is_suspended) {
return response()->json([
'status' => false,
'message' => 'Account suspended. Please check your email to reset your password.',
], 403);
}
if (! ci_password_verify($password, (string) $user->password)) {
$this->security->handleFailedLogin($user, $email, $ip);
return response()->json([
'status' => false,
'message' => 'The email and password combination you entered is invalid. Please try again.',
], 401);
}
$fresh = $user->fresh();
if (! $fresh) {
return response()->json([
'status' => false,
'message' => 'The email and password combination you entered is invalid. Please try again.',
], 401);
}
$this->security->resetFailedAttempts($fresh);
$this->security->logSuccessfulLogin($fresh, $request);
$dest = $this->sessionAuth->resolvePostLoginDestination($fresh, $sanitizedRedirect);
if (($dest['kind'] ?? '') === 'no_roles') {
return response()->json([
'status' => false,
'message' => (string) ($dest['reason'] ?? 'Role not assigned. Please contact support.'),
], 403);
}
return response()->json([
'status' => true,
'requires_role_selection' => ($dest['kind'] ?? '') === 'select_role',
'roles' => $dest['roles'] ?? null,
'next_url' => $dest['redirect_url'] ?? $this->urls->docsHomeUrl(false),
]);
}
/**
* CI `logout` — GET /logout.
*/
public function logout(Request $request): JsonResponse
{
$userId = Auth::guard('web')->id();
$email = Auth::guard('web')->user()?->email ?? session('user_email');
$this->sessionAuth->logLogout($userId ? (int) $userId : null, $email ? (string) $email : null);
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return response()->json([
'status' => true,
'next_url' => $this->urls->docsHomeUrl(false),
]);
}
/**
* CI `selectRole` — GET /select-role.
*/
public function selectRole(Request $request): JsonResponse
{
if (! Auth::guard('web')->check()) {
return response()->json([
'status' => false,
'message' => 'No roles available.',
], 401);
}
/** @var list<string>|null $roles */
$roles = session('roles');
$roles = is_array($roles) ? array_values(array_filter(array_map('strval', $roles))) : [];
if ($roles === []) {
return response()->json([
'status' => false,
'message' => 'No roles available.',
], 422);
}
return response()->json([
'status' => true,
'roles' => $roles,
'pending_redirect' => session('post_login_redirect'),
'redirect_to_query' => $this->sessionAuth->sanitizeRedirectTarget(
(string) ($request->query('redirect_to') ?? ''),
),
]);
}
/**
* CI `setRole` — POST /set-role.
*/
public function setRole(SetRoleSessionRequest $request): JsonResponse
{
if (! Auth::guard('web')->check()) {
return response()->json([
'status' => false,
'message' => 'Unauthorized.',
], 401);
}
$selectedRole = (string) $request->validated('selected_role');
/** @var list<string>|null $available */
$available = session('roles');
$available = is_array($available) ? array_values(array_filter(array_map('strval', $available))) : [];
if ($available === [] || ! in_array($selectedRole, $available, true)) {
return response()->json([
'status' => false,
'message' => 'Invalid role selected.',
], 422);
}
$redirectRaw = $request->validated('redirect_to');
$redirectTo = $redirectRaw !== null
? $this->sessionAuth->sanitizeRedirectTarget((string) $redirectRaw)
: null;
if ($redirectTo === null) {
$redirectTo = $this->sessionAuth->sanitizeRedirectTarget(
(string) (session('post_login_redirect') ?? ''),
);
}
session(['role' => $selectedRole]);
session()->forget('post_login_redirect');
$next = $redirectTo ?? $this->sessionAuth->dashboardRouteForRoles([$selectedRole]);
return response()->json([
'status' => true,
'role' => $selectedRole,
'next_url' => $next,
]);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Security\IpBanIndexRequest;
use App\Http\Requests\Security\IpBanRequest;
use App\Http\Requests\Security\IpUnbanRequest;
@@ -2,8 +2,9 @@
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Resources\Auth\RegisterResource;
use App\Services\Auth\CodeIgniterApiRegistrationService;
use App\Services\Auth\RegistrationCaptchaService;
use App\Services\Auth\RegistrationService;
use Illuminate\Http\JsonResponse;
@@ -31,6 +32,11 @@ class RegisterController extends BaseApiController
public function store(Request $request): JsonResponse
{
// CodeIgniter-style JSON register (POST api/v1/register): password + no captcha field.
if (! $request->has('captcha') && $request->filled('password')) {
return app(CodeIgniterApiRegistrationService::class)->registerResponse($request);
}
if (app()->runningUnitTests() && $request->header('X-Debug-Request') === '1') {
return response()->json([
'ok' => true,
@@ -0,0 +1,264 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Roles\AssignUserRolesRequest;
use App\Http\Requests\Roles\PermissionStoreRequest;
use App\Http\Requests\Roles\PermissionUpdateRequest;
use App\Http\Requests\Roles\RoleListRequest;
use App\Http\Requests\Roles\RolePermissionUpdateRequest;
use App\Http\Requests\Roles\RoleStoreRequest;
use App\Http\Requests\Roles\RoleUpdateRequest;
use App\Http\Requests\Roles\UserRoleListRequest;
use App\Http\Resources\Roles\PermissionResource;
use App\Http\Resources\Roles\RolePermissionResource;
use App\Http\Resources\Roles\RoleResource;
use App\Http\Resources\Roles\UserWithRolesResource;
use App\Models\Permission;
use App\Models\Role;
use App\Services\Roles\PermissionCrudService;
use App\Services\Roles\RoleAssignmentService;
use App\Services\Roles\RoleCrudService;
use App\Services\Roles\RolePermissionService;
use App\Services\Roles\RoleQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class RolePermissionController extends BaseApiController
{
public function __construct(
private RoleQueryService $queryService,
private RoleAssignmentService $assignmentService,
private RolePermissionService $permissionService,
private RoleCrudService $roleCrudService,
private PermissionCrudService $permissionCrudService
) {
parent::__construct();
}
public function roles(RoleListRequest $request): JsonResponse
{
$roles = $this->queryService->listRoles($request->validated());
return $this->success([
'roles' => RoleResource::collection($roles->items()),
'meta' => [
'total' => $roles->total(),
'per_page' => $roles->perPage(),
'current_page' => $roles->currentPage(),
'last_page' => $roles->lastPage(),
],
]);
}
public function showRole(int $roleId): JsonResponse
{
$role = Role::query()->find($roleId);
if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'role' => new RoleResource($role),
]);
}
public function storeRole(RoleStoreRequest $request): JsonResponse
{
try {
$role = $this->roleCrudService->create($request->validated());
} catch (\Throwable $e) {
Log::error('Role store failed: ' . $e->getMessage());
return $this->error('Failed to create role.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'role' => new RoleResource($role),
], 'Role created successfully.', Response::HTTP_CREATED);
}
public function updateRole(RoleUpdateRequest $request, int $roleId): JsonResponse
{
$role = Role::query()->find($roleId);
if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
}
try {
$role = $this->roleCrudService->update($role, $request->validated());
} catch (\Throwable $e) {
Log::error('Role update failed: ' . $e->getMessage());
return $this->error('Failed to update role.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'role' => new RoleResource($role),
], 'Role updated successfully.');
}
public function deleteRole(int $roleId): JsonResponse
{
$role = Role::query()->find($roleId);
if (!$role) {
return $this->error('Role not found.', Response::HTTP_NOT_FOUND);
}
try {
$this->roleCrudService->delete($role);
} catch (\Throwable $e) {
Log::error('Role delete failed: ' . $e->getMessage());
return $this->error('Failed to delete role.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Role deleted successfully.');
}
public function users(UserRoleListRequest $request): JsonResponse
{
$users = $this->queryService->listUsersWithRoles($request->validated());
return $this->success([
'users' => UserWithRolesResource::collection($users->items()),
'meta' => [
'total' => $users->total(),
'per_page' => $users->perPage(),
'current_page' => $users->currentPage(),
'last_page' => $users->lastPage(),
],
]);
}
public function assignRoles(AssignUserRolesRequest $request, int $userId): JsonResponse
{
$actorId = $this->authenticatedUserIdOrUnauthorized();
if ($actorId instanceof JsonResponse) {
return $actorId;
}
try {
$result = $this->assignmentService->assignRoles(
$userId,
(array) ($request->validated()['role_ids'] ?? []),
$actorId
);
} catch (\Throwable $e) {
Log::error('Assign roles failed: ' . $e->getMessage());
return $this->error('Failed to update roles.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update roles.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
return $this->success([
'role_names' => $result['role_names'] ?? [],
], 'Roles updated successfully.');
}
public function permissions(): JsonResponse
{
$permissions = $this->permissionService->listPermissions();
return $this->success([
'permissions' => PermissionResource::collection($permissions),
]);
}
public function showPermission(int $permissionId): JsonResponse
{
$permission = Permission::query()->find($permissionId);
if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'permission' => new PermissionResource($permission),
]);
}
public function storePermission(PermissionStoreRequest $request): JsonResponse
{
try {
$permission = $this->permissionCrudService->create($request->validated());
} catch (\Throwable $e) {
Log::error('Permission store failed: ' . $e->getMessage());
return $this->error('Failed to create permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'permission' => new PermissionResource($permission),
], 'Permission created successfully.', Response::HTTP_CREATED);
}
public function updatePermission(PermissionUpdateRequest $request, int $permissionId): JsonResponse
{
$permission = Permission::query()->find($permissionId);
if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
}
try {
$permission = $this->permissionCrudService->update($permission, $request->validated());
} catch (\Throwable $e) {
Log::error('Permission update failed: ' . $e->getMessage());
return $this->error('Failed to update permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success([
'permission' => new PermissionResource($permission),
], 'Permission updated successfully.');
}
public function deletePermission(int $permissionId): JsonResponse
{
$permission = Permission::query()->find($permissionId);
if (!$permission) {
return $this->error('Permission not found.', Response::HTTP_NOT_FOUND);
}
try {
$this->permissionCrudService->delete($permission);
} catch (\Throwable $e) {
Log::error('Permission delete failed: ' . $e->getMessage());
return $this->error('Failed to delete permission.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Permission deleted successfully.');
}
public function rolePermissions(int $roleId): JsonResponse
{
$rows = $this->permissionService->listRolePermissions($roleId);
return $this->success([
'permissions' => RolePermissionResource::collection($rows),
]);
}
public function updateRolePermissions(RolePermissionUpdateRequest $request, int $roleId): JsonResponse
{
try {
$this->permissionService->saveRolePermissions($roleId, $request->validated()['permissions'] ?? []);
} catch (\Throwable $e) {
Log::error('Role permissions update failed: ' . $e->getMessage());
return $this->error('Failed to update role permissions.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->success(null, 'Permissions saved successfully.');
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Roles\RoleSwitchRequest;
use App\Services\Roles\RoleSwitchService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class RoleSwitcherController extends BaseApiController
{
public function __construct(private RoleSwitchService $switchService)
{
parent::__construct();
}
public function index(): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$roles = $this->switchService->getUserRoleNames($guard);
if (empty($roles)) {
return $this->error('No roles assigned to this user.', Response::HTTP_NOT_FOUND);
}
return $this->success([
'roles' => $roles,
]);
}
public function switch(RoleSwitchRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$role = (string) $request->validated()['role'];
$route = $this->switchService->switchRole($role);
return $this->success([
'role' => $role,
'dashboard_route' => $route,
], 'Role switched successfully.');
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Please log in first.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Config\SessionTimeout;
use App\Http\Controllers\Controller;
use App\Services\ApplicationUrlService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Session idle timeout checks (web session cookie).
*/
class SessionTimeoutController extends Controller
{
/** CI-compatible session key */
private const SESSION_KEY = 'last_activity';
public function __construct(
private ApplicationUrlService $urls,
) {
}
/**
* CI nested routes: SessionController::getTimeoutConfig shape (URLs point at this app).
*/
public function getTimeoutConfig(): JsonResponse
{
return response()->json([
'success' => true,
'timeout' => SessionTimeout::TIMEOUT_DURATION,
'warning_time' => SessionTimeout::WARNING_THRESHOLD,
'check_interval' => SessionTimeout::CHECK_INTERVAL,
'logout_url' => $this->urls->webLogoutUrl(),
'keep_alive_url' => $this->urls->webSessionPingUrl(),
'check_url' => $this->urls->webSessionCheckTimeoutUrl(),
]);
}
public function checkTimeout(Request $request): JsonResponse
{
$session = $request->session();
if (! $session->has(self::SESSION_KEY)) {
return $this->expireSession($request);
}
$lastActivity = (int) $session->get(self::SESSION_KEY);
$elapsed = time() - $lastActivity;
if ($elapsed > SessionTimeout::TIMEOUT_DURATION) {
return $this->expireSession($request);
}
if ($elapsed > SessionTimeout::WARNING_THRESHOLD) {
return response()->json([
'status' => 'warning',
'time_remaining' => SessionTimeout::TIMEOUT_DURATION - $elapsed,
]);
}
return response()->json([
'status' => 'active',
'time_remaining' => SessionTimeout::TIMEOUT_DURATION - $elapsed,
]);
}
public function pingActivity(Request $request): JsonResponse
{
$session = $request->session();
if (! $session->has(self::SESSION_KEY)
|| (time() - (int) $session->get(self::SESSION_KEY)) > SessionTimeout::TIMEOUT_DURATION) {
return $this->expireSession($request);
}
$session->put(self::SESSION_KEY, time());
return response()->json([
'status' => 'active',
'time_remaining' => SessionTimeout::TIMEOUT_DURATION,
]);
}
private function expireSession(Request $request): JsonResponse
{
$request->session()->flash('error', 'Your session has expired due to inactivity.');
$request->session()->forget(self::SESSION_KEY);
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return response()->json([
'status' => 'expired',
'redirect' => $this->urls->webLoginUrl(),
'message' => 'Your session has expired due to inactivity.',
]);
}
}
@@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Api\BadgeScan;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Services\BadgeScan\BadgeScanService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response;
class BadgeScanController extends BaseApiController
{
public function __construct(
private BadgeScanService $badgeScan,
) {
parent::__construct();
}
/**
* Public kiosk endpoint — POST JSON or form (legacy CI RFIDController::process).
*/
public function scan(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'badge_scan' => ['required', 'string', 'max:255'],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
$result = $this->badgeScan->processScan($validator->validated()['badge_scan']);
if (! $result['recognized']) {
return $this->error($result['message'], Response::HTTP_NOT_FOUND);
}
return $this->success([
'recognized' => true,
'user_id' => $result['user_id'] ?? null,
'display_name' => $result['display_name'] ?? null,
], $result['message']);
}
/**
* Authenticated scan log listing (legacy CI RFIDController::log).
*/
public function logs(): JsonResponse
{
return $this->success([
'logs' => $this->badgeScan->scanLogRows(),
]);
}
}
@@ -22,14 +22,30 @@ class BadgeController extends Controller
public function generatePdf(Request $request)
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$validator = Validator::make($request->all(), [
'user_ids' => ['required', 'array', 'min:1'],
'user_ids.*' => ['integer', 'min:1'],
'user_ids' => ['nullable', 'array'],
'user_ids.*' => ['integer', 'exists:users,id'],
'student_ids' => ['nullable', 'array'],
'student_ids.*' => ['integer', 'exists:students,id'],
'school_year' => ['nullable', 'string', 'max:50'],
'roles' => ['nullable', 'array'],
'classes' => ['nullable', 'array'],
]);
$validator->after(function ($validator): void {
$data = $validator->getData();
$users = array_values(array_filter((array) ($data['user_ids'] ?? []), static fn ($v) => (int) $v > 0));
$students = array_values(array_filter((array) ($data['student_ids'] ?? []), static fn ($v) => (int) $v > 0));
if ($users === [] && $students === []) {
$validator->errors()->add('user_ids', 'Provide at least one user id or student id.');
}
});
if ($validator->fails()) {
return response()->json([
'message' => 'Validation failed.',
@@ -40,11 +56,12 @@ class BadgeController extends Controller
$data = $validator->validated();
return $this->badgePdfService->generate(
studentIds: $data['student_ids'] ?? [],
userIds: $data['user_ids'] ?? [],
schoolYear: $data['school_year'] ?? null,
rolesMap: $data['roles'] ?? [],
classesMap: $data['classes'] ?? [],
actorId: optional($request->user())->id
actorId: $guard
);
}
@@ -54,7 +71,7 @@ class BadgeController extends Controller
return response()->json([
'ok' => true,
'data' => $this->badgePrintLogService->getStatus(
$this->parseUserIds($request),
$this->parseCombinedBadgeIds($request),
$request->input('school_year')
),
]);
@@ -69,14 +86,30 @@ class BadgeController extends Controller
public function logPrint(Request $request): JsonResponse
{
try {
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$validator = Validator::make($request->all(), [
'user_ids' => ['required', 'array', 'min:1'],
'user_ids.*' => ['integer', 'min:1'],
'user_ids' => ['nullable', 'array'],
'user_ids.*' => ['integer', 'exists:users,id'],
'student_ids' => ['nullable', 'array'],
'student_ids.*' => ['integer', 'exists:students,id'],
'school_year' => ['nullable', 'string', 'max:50'],
'roles' => ['nullable', 'array'],
'classes' => ['nullable', 'array'],
]);
$validator->after(function ($validator): void {
$data = $validator->getData();
$users = array_values(array_filter((array) ($data['user_ids'] ?? []), static fn ($v) => (int) $v > 0));
$students = array_values(array_filter((array) ($data['student_ids'] ?? []), static fn ($v) => (int) $v > 0));
if ($users === [] && $students === []) {
$validator->errors()->add('user_ids', 'Provide at least one user id or student id.');
}
});
if ($validator->fails()) {
return response()->json([
'message' => 'Validation failed.',
@@ -87,8 +120,11 @@ class BadgeController extends Controller
$data = $validator->validated();
$inserted = $this->badgePrintLogService->log(
userIds: $data['user_ids'] ?? [],
actorId: optional($request->user())->id,
userIds: array_values(array_unique(array_merge(
$data['user_ids'] ?? [],
$data['student_ids'] ?? []
))),
actorId: $guard,
schoolYear: $data['school_year'] ?? null,
rolesMap: $data['roles'] ?? [],
classesMap: $data['classes'] ?? []
@@ -112,24 +148,75 @@ class BadgeController extends Controller
'ok' => true,
'data' => $this->badgeFormDataService->build(
schoolYear: $request->input('school_year'),
selectedUserIds: $this->parseUserIds($request),
selectedUserIds: $this->parseStaffUserIds($request),
activeRole: $request->input('active_role')
),
]);
}
private function parseUserIds(Request $request): array
/**
* Staff user IDs for the badge picker (`users.id`).
*
* @return int[]
*/
private function parseStaffUserIds(Request $request): array
{
$userIds = $request->input('user_ids', $request->query('user_ids', []));
$ids = $request->input('user_ids', $request->query('user_ids', []));
if (is_string($userIds)) {
$userIds = array_filter(array_map('trim', explode(',', $userIds)), 'strlen');
} elseif (!is_array($userIds)) {
$userIds = $userIds ? [$userIds] : [];
if (is_string($ids)) {
$ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen');
} elseif (!is_array($ids)) {
$ids = $ids ? [$ids] : [];
}
$userIds = array_values(array_unique(array_map(static fn ($v) => (int) $v, $userIds)));
$ids = array_values(array_unique(array_map(static fn ($v) => (int) $v, $ids)));
return array_values(array_filter($userIds, static fn ($v) => $v > 0));
return array_values(array_filter($ids, static fn ($v) => $v > 0));
}
/**
* Combined `users.id` and `students.id` for print-status (same numeric space as stored in logs).
*
* @return int[]
*/
private function parseCombinedBadgeIds(Request $request): array
{
return array_values(array_unique(array_merge(
$this->parseStaffUserIds($request),
$this->parseStudentIds($request)
)));
}
/**
* Student primary keys (`students.id`) from query or body.
*
* @return int[]
*/
private function parseStudentIds(Request $request): array
{
$ids = $request->input('student_ids', $request->query('student_ids', []));
if (is_string($ids)) {
$ids = array_filter(array_map('trim', explode(',', $ids)), 'strlen');
} elseif (!is_array($ids)) {
$ids = $ids ? [$ids] : [];
}
$ids = array_values(array_unique(array_map(static fn ($v) => (int) $v, $ids)));
return array_values(array_filter($ids, static fn ($v) => $v > 0));
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\ClassPreparation;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\ClassPreparation\ClassPreparationAdjustmentRequest;
use App\Http\Requests\ClassPreparation\ClassPreparationIndexRequest;
use App\Http\Requests\ClassPreparation\ClassPreparationMarkPrintedRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\ClassProgress;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\ClassProgress\ClassProgressIndexRequest;
use App\Http\Requests\ClassProgress\ClassProgressMetaRequest;
use App\Http\Requests\ClassProgress\ClassProgressStoreRequest;
@@ -12,6 +12,7 @@ use App\Http\Resources\ClassProgress\ClassProgressReportResource;
use App\Http\Resources\ClassProgress\ClassProgressShowResource;
use App\Models\ClassProgressAttachment;
use App\Models\ClassProgressReport;
use App\Models\User;
use App\Services\ClassProgress\ClassProgressAttachmentService;
use App\Services\ClassProgress\ClassProgressMetaService;
use App\Services\ClassProgress\ClassProgressMutationService;
@@ -19,6 +20,7 @@ use App\Services\ClassProgress\ClassProgressQueryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
class ClassProgressController extends BaseApiController
{
@@ -40,7 +42,7 @@ class ClassProgressController extends BaseApiController
$payload = [
'subject_sections' => $this->metaService->subjectSections(),
'status_options' => $this->metaService->statusOptions(),
'sunday_options' => $this->metaService->sundayOptions($sundayCount),
'sunday_options' => $this->metaService->sundayOptionsAlignedWithCi($sundayCount),
'curriculum' => $this->metaService->curriculumOptions($classId ? (int) $classId : null),
];
@@ -49,8 +51,13 @@ class ClassProgressController extends BaseApiController
public function index(ClassProgressIndexRequest $request): JsonResponse
{
$auth = $this->requireAuthenticatedUser($request->user());
if ($auth instanceof JsonResponse) {
return $auth;
}
$filters = $request->validated();
$paginator = $this->queryService->listReports($request->user(), $filters);
$paginator = $this->queryService->listReports($auth, $filters);
if (!empty($filters['group_by_week'])) {
$grouped = ClassProgressGroupCollection::fromPaginator($paginator);
@@ -70,10 +77,23 @@ class ClassProgressController extends BaseApiController
public function store(ClassProgressStoreRequest $request): JsonResponse
{
$auth = $this->requireAuthenticatedUser($request->user());
if ($auth instanceof JsonResponse) {
return $auth;
}
try {
$filesBySubject = $request->filesBySubject();
$reports = $this->mutationService->createReports($request->user(), $request->validated(), $filesBySubject);
$reports = $this->mutationService->createReports($auth, $request->validated(), $filesBySubject);
} catch (\InvalidArgumentException $e) {
if ($e->getMessage() === 'CONFIRM_OVERWRITE') {
return response()->json([
'status' => false,
'message' => 'A progress report already exists for this week. Resubmit with confirm_overwrite=true to replace it.',
'data' => ['requires_confirmation' => true],
], 409);
}
return $this->respondError($e->getMessage(), 422);
} catch (\Throwable $e) {
Log::error('Failed to create class progress reports.', [
@@ -87,7 +107,12 @@ class ClassProgressController extends BaseApiController
public function show(ClassProgressReport $class_progress): JsonResponse
{
$weeklyReports = $this->queryService->weeklyReports($this->laravelRequest->user(), $class_progress);
$auth = $this->requireAuthenticatedUser($this->laravelRequest->user());
if ($auth instanceof JsonResponse) {
return $auth;
}
$weeklyReports = $this->queryService->weeklyReports($auth, $class_progress);
return $this->respondSuccess(
new ClassProgressShowResource([
@@ -164,4 +189,17 @@ class ClassProgressController extends BaseApiController
return response()->download($path, basename($path));
}
/**
* @param mixed $user
* @return User|JsonResponse
*/
private function requireAuthenticatedUser($user): User|JsonResponse
{
if (!$user instanceof User) {
return $this->respondError('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $user;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Classes;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Classes\ClassSectionIndexRequest;
use App\Http\Requests\Classes\ClassSectionStoreRequest;
use App\Http\Requests\Classes\ClassSectionUpdateRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Communication;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Services\Communication\CommunicationFamilyService;
use App\Services\Communication\CommunicationPreviewService;
use App\Services\Communication\CommunicationSendService;
@@ -123,6 +123,11 @@ class CommunicationController extends BaseApiController
], 404);
}
$senderId = $this->authenticatedUserIdOrUnauthorized();
if ($senderId instanceof JsonResponse) {
return $senderId;
}
$result = $this->sendService->send([
'student_id' => $studentId,
'family_id' => $familyId,
@@ -133,7 +138,7 @@ class CommunicationController extends BaseApiController
'cc' => $cc,
'bcc' => $bcc,
'student_name' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')),
'sent_by' => (int) (auth()->id() ?? 0),
'sent_by' => $senderId,
]);
if (!$result['ok']) {
@@ -175,4 +180,17 @@ class CommunicationController extends BaseApiController
}
return 'Teacher';
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\CompetitionScores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Models\Competition;
use App\Services\CompetitionScores\CompetitionScoresContextService;
use App\Services\CompetitionScores\CompetitionScoresQueryService;
@@ -33,8 +33,12 @@ class CompetitionScoresController extends BaseApiController
public function index(Request $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
[$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($userId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
[$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($guard);
$activeClassId = $this->contextService->resolveActiveClassId(
$assignments,
@@ -80,8 +84,12 @@ class CompetitionScoresController extends BaseApiController
public function edit(Request $request, int $id): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
[$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($userId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
[$assignments, $schoolYear, $semester] = $this->contextService->getTeacherContext($guard);
$allowedClassIds = $this->contextService->getAllowedClassIds($assignments);
if (empty($allowedClassIds)) {
@@ -146,8 +154,12 @@ class CompetitionScoresController extends BaseApiController
public function save(Request $request, int $id): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
[$assignments] = $this->contextService->getTeacherContext($userId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
[$assignments] = $this->contextService->getTeacherContext($guard);
$allowedClassIds = $this->contextService->getAllowedClassIds($assignments);
if (empty($allowedClassIds)) {
@@ -209,4 +221,17 @@ class CompetitionScoresController extends BaseApiController
'savedCount' => count($cleanScores),
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -0,0 +1,276 @@
<?php
namespace App\Http\Controllers\Api\Core;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Response;
class BaseApiController extends Controller
{
protected Request $laravelRequest;
protected CiRequestAdapter $request;
protected ValidatorContract|null $validator = null;
public function __construct()
{
$this->laravelRequest = request();
$this->request = new CiRequestAdapter($this->laravelRequest);
}
protected function success($data = null, string $message = 'Success', int $code = Response::HTTP_OK): JsonResponse
{
return response()->json([
'status' => true,
'message' => $message,
'data' => $data,
], $code);
}
protected function error(string $message = 'An error occurred', int $code = Response::HTTP_BAD_REQUEST, ?array $errors = null): JsonResponse
{
$payload = [
'status' => false,
'message' => $message,
];
if (!empty($errors)) {
$payload['errors'] = $errors;
}
return response()->json($payload, $code);
}
protected function respondSuccess($data = null, string $message = 'Success', int $code = Response::HTTP_OK): JsonResponse
{
return $this->success($data, $message, $code);
}
protected function respondCreated($data = null, string $message = 'Resource created successfully'): JsonResponse
{
return $this->success($data, $message, Response::HTTP_CREATED);
}
protected function respondDeleted($data = null, string $message = 'Resource deleted successfully'): JsonResponse
{
return $this->success($data, $message, Response::HTTP_OK);
}
protected function respondError(string $message = 'An error occurred', int $code = Response::HTTP_BAD_REQUEST): JsonResponse
{
return $this->error($message, $code);
}
protected function respondValidationError(array $errors, string $message = 'Validation failed'): JsonResponse
{
return $this->error($message, Response::HTTP_UNPROCESSABLE_ENTITY, $errors);
}
protected function validateRequest(array $data, array $rules): array
{
$normalized = [];
foreach ($rules as $field => $ruleSet) {
$normalized[$field] = $this->normalizeRules($ruleSet);
}
$validator = Validator::make($data, $normalized);
$this->validator = $validator;
if ($validator->fails()) {
return $validator->errors()->toArray();
}
return [];
}
protected function validate(array $rules): bool
{
$payload = $this->payloadData();
$errors = $this->validateRequest($payload, $rules);
return empty($errors);
}
protected function payloadData(): array
{
$json = json_decode($this->laravelRequest->getContent() ?: '', true);
if (is_array($json)) {
return $json;
}
return array_merge($this->laravelRequest->query->all(), $this->laravelRequest->request->all());
}
protected function normalizeRules(array|string $ruleSet): array|string
{
if (is_array($ruleSet)) {
return $ruleSet;
}
$parts = explode('|', $ruleSet);
$result = [];
foreach ($parts as $part) {
$part = trim($part);
if ($part === '') {
continue;
}
if (preg_match('/^max_length\\[(\\d+)\\]$/', $part, $m)) {
$result[] = 'max:' . $m[1];
continue;
}
if (preg_match('/^min_length\\[(\\d+)\\]$/', $part, $m)) {
$result[] = 'min:' . $m[1];
continue;
}
if (preg_match('/^in_list\\[(.+)\\]$/', $part, $m)) {
$result[] = 'in:' . $m[1];
continue;
}
if (preg_match('/^greater_than_equal_to\\[(.+)\\]$/', $part, $m)) {
$result[] = 'gte:' . $m[1];
continue;
}
if (preg_match('/^less_than_equal_to\\[(.+)\\]$/', $part, $m)) {
$result[] = 'lte:' . $m[1];
continue;
}
if (preg_match('/^valid_date\\[(.+)\\]$/', $part, $m)) {
$result[] = 'date_format:' . $m[1];
continue;
}
if ($part === 'valid_email') {
$result[] = 'email';
continue;
}
if ($part === 'permit_empty') {
$result[] = 'nullable';
continue;
}
if (preg_match('/^is_unique\\[([^\\.]+)\\.([^\\]]+)\\]$/', $part, $m)) {
$result[] = 'unique:' . $m[1] . ',' . $m[2];
continue;
}
if ($part === 'string') {
$result[] = 'string';
continue;
}
if ($part === 'integer') {
$result[] = 'integer';
continue;
}
if ($part === 'numeric') {
$result[] = 'numeric';
continue;
}
if ($part === 'required') {
$result[] = 'required';
continue;
}
if ($part === 'alpha_numeric') {
$result[] = 'alpha_num';
continue;
}
if ($part === 'in_array') {
$result[] = 'in_array';
continue;
}
if ($part === 'valid_url') {
$result[] = 'url';
continue;
}
if (strpos($part, 'max_size') === 0) {
$result[] = str_replace('max_size', 'max', $part);
continue;
}
$result[] = $part;
}
return $result;
}
protected function paginate($source, int $page = 1, int $perPage = 20): array
{
$page = max(1, $page);
$perPage = max(1, $perPage);
$offset = ($page - 1) * $perPage;
if ($source instanceof EloquentBuilder || $source instanceof \Illuminate\Database\Query\Builder) {
$total = $source->toBase()->getCountForPagination();
$items = $source->offset($offset)->limit($perPage)->get()->toArray();
} elseif (is_array($source)) {
$total = count($source);
$items = array_slice($source, $offset, $perPage);
} else {
return [
'data' => [],
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => 0,
'total_pages' => 0,
],
];
}
return [
'data' => array_values($items),
'pagination' => [
'current_page' => $page,
'per_page' => $perPage,
'total' => $total,
'total_pages' => (int) ceil($total / $perPage),
],
];
}
protected function getCurrentUserId(): ?int
{
$userId = (int) (auth()->id() ?? 0);
return $userId > 0 ? $userId : null;
}
protected function getCurrentUser(): ?object
{
$userId = $this->getCurrentUserId();
if (!$userId) {
return null;
}
$user = app(User::class);
$row = $user->find($userId);
if (!$row) {
return null;
}
$name = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
if ($name === '') {
$name = $row['email'] ?? 'User #' . $userId;
}
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) [
'id' => (int) $userId,
'name' => $name,
'email' => $row['email'] ?? null,
'roles' => $roles,
];
}
}
@@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Api\Core;
use Illuminate\Http\Request;
class CiRequestAdapter
{
public function __construct(protected Request $request)
{
}
public function getGet(string $key = null, $default = null)
{
if ($key === null) {
return $this->request->query->all();
}
return $this->request->query->get($key, $default);
}
public function getPost(string $key = null, $default = null)
{
if ($key === null) {
return $this->request->request->all();
}
return $this->request->request->get($key, $default);
}
public function getJSON(bool $assoc = false)
{
$content = $this->request->getContent();
if ($content === '') {
return $assoc ? [] : null;
}
return json_decode($content, $assoc);
}
public function getVar(string $key, $default = null)
{
return $this->request->input($key, $default);
}
public function getHeader(string $key)
{
return $this->request->headers->get($key);
}
public function getHeaderLine(string $key)
{
return $this->request->header($key);
}
public function getFile(string $key)
{
return $this->request->file($key);
}
public function getRawInput(): string
{
return $this->request->getContent();
}
public function all(): array
{
return array_merge(
$this->request->query->all(),
$this->request->request->all(),
$this->request->json()->all()
);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Discounts;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Discounts\ApplyDiscountVoucherRequest;
use App\Http\Requests\Discounts\StoreDiscountVoucherRequest;
use App\Http\Requests\Discounts\UpdateDiscountVoucherRequest;
@@ -50,12 +50,17 @@ class DiscountController extends BaseApiController
public function apply(ApplyDiscountVoucherRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$result = $this->applyService->applyVoucher(
(int) $payload['voucher_id'],
$payload['parent_ids'],
(bool) ($payload['allow_additional'] ?? false),
(int) (auth()->id() ?? 0)
$guard
);
return response()->json($result, $result['ok'] ? 200 : 422);
@@ -104,4 +109,17 @@ class DiscountController extends BaseApiController
'ok' => true,
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\Documentation;
use App\Http\Controllers\Controller;
use App\Services\Docs\ApiDocsService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
/**
* Authenticated API explorer bootstrap + public read-only variant.
*/
class ApiDocsAdminController extends Controller
{
public function __construct(
private ApiDocsService $apiDocs,
) {}
public function index(): JsonResponse
{
return response()->json(
$this->apiDocs->bootstrap(Auth::user(), false),
);
}
public function public(): JsonResponse
{
return response()->json(
$this->apiDocs->bootstrap(null, true),
);
}
}
@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Api\Documentation;
use App\Http\Controllers\Controller;
use App\Services\Docs\DocsCatalogService;
use App\Services\Docs\OpenApiRouteExporter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
/**
* Docs hub + multi-spec Swagger catalog (JSON for SPA).
*/
class DocsCatalogController extends Controller
{
public function __construct(
private DocsCatalogService $catalog,
) {}
public function index(Request $request)
{
if (! $request->expectsJson()) {
return redirect()->to((string) config('docs.client_url', url('/docs')));
}
return response()->json([
'status' => true,
'data' => $this->catalog->indexPayload(),
]);
}
public function ui(): View
{
return view('docs.swagger');
}
public function swagger(): JsonResponse
{
return response()->json([
'status' => true,
'data' => $this->catalog->swaggerSpecsPayload(),
]);
}
/**
* Merged OpenAPI spec (static openapi.json + live route merge).
*/
public function swaggerMergedJson(): JsonResponse
{
$path = resource_path('docs/openapi.json');
abort_unless(is_file($path), 404);
$spec = app(OpenApiRouteExporter::class)->exportFromFile($path);
return response()->json($spec, 200, [
'Content-Type' => 'application/json',
'Cache-Control' => 'no-store, max-age=0',
]);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Email;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Services\BroadcastEmail\BroadcastEmailComposerService;
use App\Services\BroadcastEmail\BroadcastEmailDispatchService;
use App\Services\BroadcastEmail\BroadcastEmailImageService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Email;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Email\EmailSendRequest;
use App\Http\Resources\Email\EmailSenderResource;
use App\Services\Email\EmailAttachmentService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Email;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Email\EmailExtractorCompareRequest;
use App\Http\Resources\Email\EmailExtractorCompareResource;
use App\Http\Resources\Email\EmailExtractorEmailsResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Exams;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Exams\ExamDraftAdminLegacyRequest;
use App\Http\Requests\Exams\ExamDraftAdminReviewRequest;
use App\Http\Requests\Exams\ExamDraftTeacherStoreRequest;
@@ -22,12 +22,12 @@ class ExamDraftController extends BaseApiController
public function teacherIndex(): JsonResponse
{
$teacherId = (int) (auth()->id() ?? 0);
if ($teacherId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->teacherService->listForTeacher($teacherId);
$data = $this->teacherService->listForTeacher($guard);
return response()->json([
'ok' => true,
@@ -43,12 +43,12 @@ class ExamDraftController extends BaseApiController
public function teacherStore(ExamDraftTeacherStoreRequest $request): JsonResponse
{
$teacherId = (int) (auth()->id() ?? 0);
if ($teacherId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->teacherService->store($teacherId, $request->validated(), $request->file('draft_file'));
$result = $this->teacherService->store($guard, $request->validated(), $request->file('draft_file'));
if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
}
@@ -79,12 +79,12 @@ class ExamDraftController extends BaseApiController
public function adminUploadLegacy(ExamDraftAdminLegacyRequest $request): JsonResponse
{
$adminId = (int) (auth()->id() ?? 0);
if ($adminId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->adminService->uploadLegacy($adminId, $request->validated(), $request->file('old_exam_file'));
$result = $this->adminService->uploadLegacy($guard, $request->validated(), $request->file('old_exam_file'));
if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
}
@@ -97,12 +97,12 @@ class ExamDraftController extends BaseApiController
public function adminReview(ExamDraftAdminReviewRequest $request): JsonResponse
{
$adminId = (int) (auth()->id() ?? 0);
if ($adminId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->adminService->review($adminId, $request->validated(), $request->file('final_file'));
$result = $this->adminService->review($guard, $request->validated(), $request->file('final_file'));
if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Unable to save.'], 422);
}
@@ -112,4 +112,17 @@ class ExamDraftController extends BaseApiController
'draft' => new ExamDraftResource($result['draft']),
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Expenses;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Expenses\StoreExpenseRequest;
use App\Http\Requests\Expenses\UpdateExpenseRequest;
use App\Http\Requests\Expenses\UpdateExpenseStatusRequest;
@@ -77,8 +77,12 @@ class ExpenseController extends BaseApiController
public function store(StoreExpenseRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$userId = (int) (auth()->id() ?? 0);
$receiptName = null;
if ($request->hasFile('receipt')) {
@@ -95,10 +99,10 @@ class ExpenseController extends BaseApiController
'retailor' => $payload['retailor'] ?? null,
'date_of_purchase' => $payload['date_of_purchase'],
'purchased_by' => (int) $payload['purchased_by'],
'added_by' => $userId,
'added_by' => $guard,
'status' => $isDonation ? 'approved' : 'pending',
'status_reason' => $isDonation ? 'Marked as Donation (non-reimbursable).' : null,
'approved_by' => $isDonation ? $userId : null,
'approved_by' => $isDonation ? $guard : null,
'school_year' => $payload['school_year'] ?? null,
'semester' => $payload['semester'] ?? null,
]);
@@ -111,13 +115,17 @@ class ExpenseController extends BaseApiController
public function update(UpdateExpenseRequest $request, int $id): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$expense = Expense::query()->find($id);
if (!$expense) {
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
}
$payload = $request->validated();
$userId = (int) (auth()->id() ?? 0);
$receiptName = $expense->receipt_path;
if ($request->hasFile('receipt')) {
@@ -138,13 +146,13 @@ class ExpenseController extends BaseApiController
'date_of_purchase' => $payload['date_of_purchase'],
'purchased_by' => (int) $payload['purchased_by'],
'receipt_path' => $receiptName,
'updated_by' => $userId,
'updated_by' => $guard,
];
if ($isDonation) {
$updateData['status'] = 'approved';
$updateData['status_reason'] = 'Marked as Donation (non-reimbursable).';
$updateData['approved_by'] = $userId ?: null;
$updateData['approved_by'] = $guard ?: null;
$updateData['reimbursement_id'] = null;
} elseif (($expense->category ?? '') === 'Donation') {
$updateData['status_reason'] = null;
@@ -162,19 +170,36 @@ class ExpenseController extends BaseApiController
public function updateStatus(UpdateExpenseStatusRequest $request, int $id): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$expense = Expense::query()->find($id);
if (!$expense) {
return response()->json(['ok' => false, 'message' => 'Expense not found.'], 404);
}
$payload = $request->validated();
$userId = (int) (auth()->id() ?? 0);
$updated = $this->statusService->updateStatus($expense, $payload['status'], $payload['reason'] ?? '', $userId);
$updated = $this->statusService->updateStatus($expense, $payload['status'], $payload['reason'] ?? '', $guard);
return response()->json([
'ok' => true,
'expense' => new ExpenseResource($updated->toArray()),
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\ExtraCharges;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\ExtraCharges\StoreExtraChargeRequest;
use App\Http\Requests\ExtraCharges\UpdateExtraChargeRequest;
use App\Http\Resources\ExtraCharges\ExtraChargeInvoiceOptionResource;
@@ -87,8 +87,13 @@ class ExtraChargesController extends BaseApiController
public function store(StoreExtraChargeRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$payload['created_by'] = (int) (auth()->id() ?? 0);
$payload['created_by'] = $userId;
$result = $this->chargeService->createCharge($payload);
@@ -102,8 +107,13 @@ class ExtraChargesController extends BaseApiController
return response()->json(['ok' => false, 'error' => 'Charge not found'], 404);
}
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$payload['updated_by'] = (int) (auth()->id() ?? 0);
$payload['updated_by'] = $userId;
$ok = $this->chargeService->updateCharge($charge, $payload);
@@ -140,4 +150,17 @@ class ExtraChargesController extends BaseApiController
'ok' => $ok,
], $ok ? 200 : 422);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Family;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Families\FamilyAdminCardRequest;
use App\Http\Requests\Families\FamilyAdminIndexRequest;
use App\Http\Requests\Families\FamilyAdminSearchRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Family;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Families\FamiliesByStudentRequest;
use App\Http\Requests\Families\FamilyAttachSecondByEmailRequest;
use App\Http\Requests\Families\FamilyAttachSecondByUserRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Finance\FeeRefundRequest;
use App\Http\Requests\Finance\FeeTuitionTotalRequest;
use App\Http\Resources\Fees\FeeRefundResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Finance\FinancialReportRequest;
use App\Http\Requests\Finance\FinancialSummaryRequest;
use App\Http\Requests\Finance\FinancialUnpaidParentsRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Invoices\InvoiceManagementRequest;
use App\Http\Requests\Invoices\InvoiceParentRequest;
use App\Http\Requests\Invoices\InvoiceStatusRequest;
@@ -88,9 +88,9 @@ class InvoiceController extends BaseApiController
public function parentPayment(InvoiceParentRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$parentId = $this->authenticatedUserIdOrUnauthorized();
if ($parentId instanceof JsonResponse) {
return $parentId;
}
$schoolYear = $request->validated()['school_year'] ?? null;
@@ -136,4 +136,17 @@ class InvoiceController extends BaseApiController
echo $pdfBytes;
}, 'invoice_' . $invoiceId . '.pdf', ['Content-Type' => 'application/pdf']);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Payments\PaymentByParentRequest;
use App\Http\Resources\Payments\PaymentResource;
use App\Services\Payments\PaymentBalanceService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Payments\PaymentEventChargesListRequest;
use App\Services\Payments\PaymentEventChargesService;
use Illuminate\Http\JsonResponse;
@@ -26,6 +26,11 @@ class PaymentEventChargesController extends BaseApiController
public function store(Request $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
$validator = Validator::make($data, [
'parent_id' => ['required', 'integer', 'min:1'],
@@ -47,9 +52,8 @@ class PaymentEventChargesController extends BaseApiController
}
$payload = $validator->validated();
$userId = (int) (auth()->id() ?? 0);
$count = $this->service->addCharges($payload, $userId);
$count = $this->service->addCharges($payload, $guard);
if ($count === 0) {
return response()->json(['ok' => false, 'message' => 'No student selected.'], 422);
}
@@ -64,4 +68,17 @@ class PaymentEventChargesController extends BaseApiController
return response()->json(['ok' => true, 'students' => $students]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Payments\PaymentManualEditRequest;
use App\Http\Requests\Payments\PaymentManualUpdateRequest;
use App\Services\Payments\PaymentManualService;
@@ -63,6 +63,11 @@ class PaymentManualController extends BaseApiController
public function record(PaymentManualUpdateRequest $request, int $invoiceId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$result = $this->service->recordPayment(
@@ -75,7 +80,7 @@ class PaymentManualController extends BaseApiController
$request->file('payment_file'),
$payload['school_year'] ?? null,
$payload['semester'] ?? null,
(int) (auth()->id() ?? 0)
$guard
);
return response()->json(['ok' => true] + $result);
@@ -83,6 +88,11 @@ class PaymentManualController extends BaseApiController
public function edit(PaymentManualEditRequest $request, int $paymentId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$this->service->editPayment(
@@ -91,7 +101,7 @@ class PaymentManualController extends BaseApiController
(string) $payload['payment_method'],
$payload['check_number'] ?? null,
$request->file('payment_file'),
(int) (auth()->id() ?? 0)
$guard
);
return response()->json(['ok' => true]);
@@ -102,4 +112,17 @@ class PaymentManualController extends BaseApiController
$mode = (string) ($request->query('mode') ?? 'download');
return $this->service->serveCheckFile($filename, $mode);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Payments\PaymentNotificationListRequest;
use App\Http\Requests\Payments\PaymentNotificationSendRequest;
use App\Http\Resources\Payments\PaymentNotificationLogResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Resources\Payments\PaymentTransactionResource;
use App\Services\Payments\PaymentTransactionService;
use Illuminate\Http\JsonResponse;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Payments\PaypalExecuteRequest;
use App\Services\Payments\PaypalPaymentService;
use Illuminate\Http\JsonResponse;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Payments\PaypalTransactionsListRequest;
use App\Http\Resources\Payments\PaypalTransactionResource;
use App\Services\Payments\PaypalTransactionsService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\PurchaseOrders\PurchaseOrderIndexRequest;
use App\Http\Resources\PurchaseOrders\PurchaseOrderItemResource;
use App\Http\Resources\PurchaseOrders\PurchaseOrderResource;
@@ -99,6 +99,10 @@ class PurchaseOrderController extends BaseApiController
}
}
if (auth()->user() === null) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
try {
$po = $this->createService->create($payload);
} catch (RuntimeException $e) {
@@ -137,7 +141,11 @@ class PurchaseOrderController extends BaseApiController
}
$user = auth()->user();
$issuedBy = $user ? (string) ($user->email ?? $user->username ?? $user->id) : 'system';
if ($user === null) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
$issuedBy = (string) ($user->email ?? $user->username ?? $user->id);
try {
$result = $this->receiveService->receive($id, $payload['items'], $issuedBy);
@@ -156,6 +164,10 @@ class PurchaseOrderController extends BaseApiController
public function cancel(int $id): JsonResponse
{
if (auth()->user() === null) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
$po = PurchaseOrder::query()->find($id);
if (!$po) {
return response()->json(['ok' => false, 'message' => 'Purchase order not found.'], 404);
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Refunds\RefundDecisionRequest;
use App\Http\Requests\Refunds\RefundIndexRequest;
use App\Http\Requests\Refunds\RefundPaymentRequest;
@@ -60,11 +60,15 @@ class RefundController extends BaseApiController
public function store(RefundStoreRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
try {
$result = $this->requestService->requestRefund($payload, $actorId);
$result = $this->requestService->requestRefund($payload, $guard);
} catch (\Throwable $e) {
Log::error('Refund request failed.', ['error' => $e->getMessage()]);
return response()->json(['ok' => false, 'message' => 'Failed to submit refund request.'], 500);
@@ -88,14 +92,18 @@ class RefundController extends BaseApiController
public function update(RefundDecisionRequest $request, int $refundId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
$result = $this->decisionService->updateDecision(
$refundId,
(string) $payload['status'],
(string) $payload['reason'],
$actorId
$guard
);
if (empty($result['ok'])) {
@@ -107,8 +115,12 @@ class RefundController extends BaseApiController
public function destroy(int $refundId): JsonResponse
{
$actorId = (int) (auth()->id() ?? 0);
$result = $this->decisionService->cancel($refundId, $actorId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->decisionService->cancel($refundId, $guard);
if (empty($result['ok'])) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to cancel refund.'], 404);
@@ -119,8 +131,12 @@ class RefundController extends BaseApiController
public function approve(int $refundId): JsonResponse
{
$actorId = (int) (auth()->id() ?? 0);
$result = $this->decisionService->approve($refundId, $actorId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->decisionService->approve($refundId, $guard);
if (empty($result['ok'])) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Approve failed.'], 422);
@@ -131,9 +147,13 @@ class RefundController extends BaseApiController
public function reject(RefundRejectRequest $request, int $refundId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
$result = $this->decisionService->reject($refundId, (string) $payload['reason'], $actorId);
$result = $this->decisionService->reject($refundId, (string) $payload['reason'], $guard);
if (empty($result['ok'])) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Reject failed.'], 422);
@@ -144,11 +164,15 @@ class RefundController extends BaseApiController
public function recordPayment(RefundPaymentRequest $request, int $refundId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$payload['check_file'] = $request->file('check_file');
$actorId = (int) (auth()->id() ?? 0);
$result = $this->payoutService->recordPayment($refundId, $payload, $actorId);
$result = $this->payoutService->recordPayment($refundId, $payload, $guard);
if (empty($result['ok'])) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to update payment.'], 422);
}
@@ -158,10 +182,14 @@ class RefundController extends BaseApiController
public function recalculateOverpayments(RefundRecalculateRequest $request): JsonResponse
{
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->overpaymentService->recalculate(true, $payload['invoice_number'] ?? null, $actorId);
$payload = $request->validated();
$result = $this->overpaymentService->recalculate(true, $payload['invoice_number'] ?? null, $guard);
return response()->json([
'ok' => true,
@@ -183,4 +211,17 @@ class RefundController extends BaseApiController
'summary' => $summary,
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Finance;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Files\FileNameRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchAdminFileRequest;
use App\Http\Requests\Reimbursements\ReimbursementBatchAssignmentRequest;
@@ -68,6 +68,11 @@ class ReimbursementController extends BaseApiController
public function markDonation(Request $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
$validator = Validator::make($data, [
'expense_id' => ['required', 'integer', 'min:1'],
@@ -81,10 +86,9 @@ class ReimbursementController extends BaseApiController
}
$payload = $validator->validated();
$userId = (int) (auth()->id() ?? 0);
try {
$this->donationService->markDonation((int) $payload['expense_id'], $userId);
$this->donationService->markDonation((int) $payload['expense_id'], $guard);
} catch (RuntimeException $e) {
$message = $e->getMessage();
$status = str_contains($message, 'not found') ? 404 : (str_contains($message, 'already') ? 409 : 422);
@@ -98,6 +102,11 @@ class ReimbursementController extends BaseApiController
public function createBatch(Request $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
$validator = Validator::make($data, [
'title' => ['nullable', 'string', 'max:255'],
@@ -111,11 +120,10 @@ class ReimbursementController extends BaseApiController
}
$payload = $validator->validated();
$userId = (int) (auth()->id() ?? 0);
$schoolYear = $this->context->getSchoolYear();
$semester = $this->context->getSemester();
$batch = $this->batchService->createBatch($payload['title'] ?? null, $userId, $schoolYear, $semester);
$batch = $this->batchService->createBatch($payload['title'] ?? null, $guard, $schoolYear, $semester);
return response()->json([
'ok' => true,
@@ -158,13 +166,17 @@ class ReimbursementController extends BaseApiController
public function lockBatch(ReimbursementBatchLockRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$userId = (int) (auth()->id() ?? 0);
try {
$this->batchService->lockBatch(
(int) $payload['batch_id'],
$userId,
$guard,
$this->context->getSchoolYear(),
$this->context->getSemester()
);
@@ -181,6 +193,11 @@ class ReimbursementController extends BaseApiController
public function uploadBatchAdminFile(ReimbursementBatchAdminFileRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$batchId = (int) $payload['batch_id'];
$adminId = (int) $payload['admin_id'];
@@ -194,7 +211,7 @@ class ReimbursementController extends BaseApiController
}
try {
$file = $this->fileService->storeAdminFile($batchId, $adminId, $request->file('check_file'), (int) (auth()->id() ?? 0));
$file = $this->fileService->storeAdminFile($batchId, $adminId, $request->file('check_file'), $guard);
} catch (\Throwable $e) {
return response()->json(['ok' => false, 'message' => 'Unable to store the uploaded file.'], 500);
}
@@ -312,6 +329,11 @@ class ReimbursementController extends BaseApiController
public function store(Request $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
$validator = Validator::make($data, [
'amount' => ['required', 'numeric', 'gt:0'],
@@ -333,7 +355,6 @@ class ReimbursementController extends BaseApiController
}
$payload = $validator->validated();
$userId = (int) (auth()->id() ?? 0);
$receiptName = null;
if ($request->hasFile('receipt')) {
@@ -343,7 +364,7 @@ class ReimbursementController extends BaseApiController
try {
$reimb = $this->crudService->store(
$payload,
$userId,
$guard,
$this->context->getSchoolYear(),
$this->context->getSemester(),
$receiptName
@@ -363,8 +384,12 @@ class ReimbursementController extends BaseApiController
public function process(ReimbursementProcessRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$userId = (int) (auth()->id() ?? 0);
$receiptName = null;
if ($request->hasFile('receipt')) {
@@ -374,7 +399,7 @@ class ReimbursementController extends BaseApiController
try {
$reimb = $this->crudService->process(
$payload,
$userId,
$guard,
$this->context->getSchoolYear(),
$this->context->getSemester(),
$receiptName
@@ -404,6 +429,11 @@ class ReimbursementController extends BaseApiController
public function update(Request $request, int $id): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$reimb = Reimbursement::query()->find($id);
if (!$reimb) {
return response()->json(['ok' => false, 'message' => 'Reimbursement not found.'], 404);
@@ -428,7 +458,6 @@ class ReimbursementController extends BaseApiController
}
$payload = $validator->validated();
$userId = (int) (auth()->id() ?? 0);
$receiptName = $reimb->receipt_path;
if ($request->hasFile('receipt')) {
@@ -438,7 +467,7 @@ class ReimbursementController extends BaseApiController
$receiptName = null;
}
$updated = $this->crudService->update($reimb, $payload, $userId, $receiptName);
$updated = $this->crudService->update($reimb, $payload, $guard, $receiptName);
$data = $updated->toArray();
$data['receipt_url'] = $this->fileService->receiptUrl($updated->receipt_path ?? null);
@@ -447,4 +476,17 @@ class ReimbursementController extends BaseApiController
'reimbursement' => new ReimbursementResource($data),
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Resources\Frontend\FrontendPageResource;
use App\Http\Resources\Frontend\FrontendUserResource;
use App\Services\Frontend\FrontendPageService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Resources\Frontend\ProfileIconResource;
use App\Services\Frontend\ProfileIconService;
use Illuminate\Http\JsonResponse;
@@ -17,15 +17,28 @@ class InfoIconController extends BaseApiController
public function profileIcon(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $this->service->buildForUser($userId);
$payload = $this->service->buildForUser($guard);
return $this->success([
'profile' => new ProfileIconResource($payload),
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,11 +2,12 @@
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Frontend\LandingTeacherRequest;
use App\Http\Resources\Frontend\LandingPageResource;
use App\Services\Frontend\LandingPageService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class LandingPageController extends BaseApiController
{
@@ -17,8 +18,13 @@ class LandingPageController extends BaseApiController
public function index(LandingTeacherRequest $request): JsonResponse
{
$user = $request->user();
if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->service->dashboardForUser(
$request->user(),
$user,
$request->validated()['class_section_id'] ?? null
);
@@ -29,8 +35,13 @@ class LandingPageController extends BaseApiController
public function teacher(LandingTeacherRequest $request): JsonResponse
{
$user = $request->user();
if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->service->teacher(
$request->user(),
$user,
$request->validated()['class_section_id'] ?? null
);
@@ -41,7 +52,12 @@ class LandingPageController extends BaseApiController
public function parent(): JsonResponse
{
$payload = $this->service->parent(auth()->user());
$user = auth()->user();
if (!$user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$payload = $this->service->parent($user);
return $this->success([
'landing' => new LandingPageResource($payload),
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Frontend;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Frontend\ContactSubmitRequest;
use App\Http\Resources\Frontend\ContactSubmissionResource;
use App\Http\Resources\Frontend\PageContentResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Grading;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Grading\BelowSixtyEmailRequest;
use App\Http\Requests\Grading\BelowSixtyMeetingRequest;
use App\Http\Requests\Grading\BelowSixtyMeetingSaveRequest;
@@ -79,20 +79,30 @@ class GradingController extends BaseApiController
public function update(GradingScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->scoreService->update($payload, (int) (auth()->id() ?? 0));
$this->scoreService->update($payload, $userId);
return response()->json(['ok' => true]);
}
public function toggleLock(GradingLockRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$locked = $this->lockService->toggle(
(int) $payload['class_section_id'],
(string) $payload['semester'],
(string) $payload['school_year'],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true, 'locked' => $locked]);
@@ -100,11 +110,16 @@ class GradingController extends BaseApiController
public function lockAll(GradingLockAllRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->lockService->lockAll(
(string) $payload['semester'],
(string) $payload['school_year'],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true, 'locked_count' => $count]);
@@ -137,12 +152,17 @@ class GradingController extends BaseApiController
public function updatePlacementLevel(PlacementLevelRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->placementService->updatePlacementLevel(
(int) $payload['student_id'],
(string) $payload['school_year'],
$payload['placement_level'] ?? null,
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true]);
@@ -150,12 +170,17 @@ class GradingController extends BaseApiController
public function updatePlacementLevels(PlacementLevelsRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->placementService->updatePlacementLevels(
(int) $payload['class_section_id'],
(string) $payload['school_year'],
$payload['placement_level'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true]);
@@ -163,12 +188,17 @@ class GradingController extends BaseApiController
public function createPlacementBatch(PlacementBatchRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$batchId = $this->placementService->createPlacementBatch(
(string) $payload['school_year'],
(string) $payload['placement_test'],
$payload['placement_level'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true, 'batch_id' => $batchId]);
@@ -183,12 +213,17 @@ class GradingController extends BaseApiController
public function updatePlacementBatch(PlacementBatchUpdateRequest $request, int $batchId): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->placementService->updatePlacementBatch(
$batchId,
(string) $payload['school_year'],
$payload['placement_level'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true]);
@@ -246,6 +281,11 @@ class GradingController extends BaseApiController
public function updateBelowSixtyStatus(BelowSixtyStatusRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->belowSixtyService->updateStatus(
(int) $payload['student_id'],
@@ -253,7 +293,7 @@ class GradingController extends BaseApiController
(string) $payload['school_year'],
(string) $payload['status'],
(string) ($payload['note'] ?? ''),
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true]);
@@ -273,9 +313,27 @@ class GradingController extends BaseApiController
public function saveBelowSixtyMeeting(BelowSixtyMeetingSaveRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->belowSixtyService->saveMeeting($payload, (int) (auth()->id() ?? 0));
$this->belowSixtyService->saveMeeting($payload, $userId);
return response()->json(['ok' => true]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Grading;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Grading\HomeworkTrackingRequest;
use App\Http\Resources\Grading\HomeworkTrackingTeacherResource;
use App\Services\Grading\HomeworkTrackingService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Incidents;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Incidents\IncidentCancelRequest;
use App\Http\Requests\Incidents\IncidentCloseRequest;
use App\Http\Requests\Incidents\IncidentListRequest;
@@ -86,6 +86,11 @@ class IncidentController extends BaseApiController
public function store(Request $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
$validator = Validator::make($data, [
'student_id' => ['required', 'integer', 'min:1'],
@@ -104,7 +109,7 @@ class IncidentController extends BaseApiController
}
$payload = $validator->validated();
$result = $this->currentService->addIncident($payload, (int) (auth()->id() ?? 0));
$result = $this->currentService->addIncident($payload, $guard);
if (empty($result['ok'])) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to add incident.'], 422);
@@ -129,6 +134,11 @@ class IncidentController extends BaseApiController
public function close(Request $request, int $incidentId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = array_merge($request->query->all(), $request->all(), $request->json()->all());
$validator = Validator::make($data, [
'state_description' => ['required', 'string', 'max:2000'],
@@ -147,7 +157,7 @@ class IncidentController extends BaseApiController
$incidentId,
(string) $payload['state_description'],
$payload['action_taken'] ?? null,
(int) (auth()->id() ?? 0)
$guard
);
if (empty($result['ok'])) {
@@ -162,12 +172,17 @@ class IncidentController extends BaseApiController
public function cancel(IncidentCancelRequest $request, int $incidentId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$result = $this->currentService->cancelIncident(
$incidentId,
$payload['state_description'] ?? null,
$payload['action_taken'] ?? null,
(int) (auth()->id() ?? 0)
$guard
);
if (empty($result['ok'])) {
@@ -179,4 +194,17 @@ class IncidentController extends BaseApiController
'incident_id' => $result['incident_id'],
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Inventory;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Inventory\InventoryCategoryIndexRequest;
use App\Http\Requests\Inventory\InventoryCategoryStoreRequest;
use App\Http\Requests\Inventory\InventoryCategoryUpdateRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Inventory;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Inventory\InventoryAdjustRequest;
use App\Http\Requests\Inventory\InventoryAuditRequest;
use App\Http\Requests\Inventory\InventoryItemIndexRequest;
@@ -57,7 +57,12 @@ class InventoryController extends BaseApiController
public function store(InventoryItemStoreRequest $request): JsonResponse
{
$result = $this->items->create($request->validated(), (int) auth()->id());
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$result = $this->items->create($request->validated(), $actor);
if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to create item.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
@@ -67,7 +72,12 @@ class InventoryController extends BaseApiController
public function update(InventoryItemUpdateRequest $request, int $id): JsonResponse
{
$result = $this->items->update($id, $request->validated(), (int) auth()->id());
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$result = $this->items->update($id, $request->validated(), $actor);
if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to update item.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
@@ -87,7 +97,12 @@ class InventoryController extends BaseApiController
public function audit(InventoryAuditRequest $request, int $id): JsonResponse
{
$result = $this->items->auditClassroom($id, $request->validated(), (int) auth()->id());
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$result = $this->items->auditClassroom($id, $request->validated(), $actor);
if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to audit item.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
@@ -97,7 +112,12 @@ class InventoryController extends BaseApiController
public function adjust(InventoryAdjustRequest $request, int $id): JsonResponse
{
$result = $this->items->adjustStock($id, $request->validated(), (int) auth()->id());
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$result = $this->items->adjustStock($id, $request->validated(), $actor);
if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to adjust stock.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
@@ -122,9 +142,14 @@ class InventoryController extends BaseApiController
public function teacherDistribution(InventoryTeacherDistributionRequest $request): JsonResponse
{
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$payload = $request->validated();
$result = $this->distribution->formData(
(int) auth()->id(),
$actor,
$payload['class_section_id'] ?? null,
$payload['item_id'] ?? null
);
@@ -138,8 +163,13 @@ class InventoryController extends BaseApiController
public function teacherDistributionStore(InventoryTeacherDistributionStoreRequest $request): JsonResponse
{
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
try {
$result = $this->distribution->distribute((int) auth()->id(), $request->validated());
$result = $this->distribution->distribute($actor, $request->validated());
} catch (\Throwable $e) {
Log::error('Inventory distribution failed: ' . $e->getMessage());
return $this->error('Unable to distribute inventory.', Response::HTTP_INTERNAL_SERVER_ERROR);
@@ -151,4 +181,17 @@ class InventoryController extends BaseApiController
return $this->success($result);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Inventory;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Inventory\InventoryMovementBulkDeleteRequest;
use App\Http\Requests\Inventory\InventoryMovementIndexRequest;
use App\Http\Requests\Inventory\InventoryMovementStoreRequest;
@@ -30,7 +30,12 @@ class InventoryMovementController extends BaseApiController
public function store(InventoryMovementStoreRequest $request): JsonResponse
{
$result = $this->movements->create($request->validated(), (int) auth()->id());
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$result = $this->movements->create($request->validated(), $actor);
if (!($result['ok'] ?? false)) {
return $this->error($result['message'] ?? 'Failed to create movement.', Response::HTTP_UNPROCESSABLE_ENTITY);
}
@@ -67,4 +72,17 @@ class InventoryMovementController extends BaseApiController
return $this->success($result);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Inventory;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Inventory\SupplierIndexRequest;
use App\Http\Requests\Inventory\SupplierStoreRequest;
use App\Http\Requests\Inventory\SupplierUpdateRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Inventory;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Inventory\SupplyCategoryStoreRequest;
use App\Http\Requests\Inventory\SupplyCategoryUpdateRequest;
use App\Http\Resources\Inventory\SupplyCategoryResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Messaging;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Messaging\MessageIndexRequest;
use App\Http\Requests\Messaging\MessageSendRequest;
use App\Http\Requests\Messaging\MessageUpdateRequest;
@@ -29,9 +29,9 @@ class MessagesController extends BaseApiController
public function index(MessageIndexRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$filters = $request->validated();
@@ -40,8 +40,8 @@ class MessagesController extends BaseApiController
$this->authorize('viewAny', Message::class);
$role = $this->queryService->getUserRoleName($userId);
$messages = $this->queryService->inbox($userId, $filters, $page, $perPage);
$role = $this->queryService->getUserRoleName($guard);
$messages = $this->queryService->inbox($guard, $filters, $page, $perPage);
$collection = new MessageCollection($messages);
$meta = $collection->with($request)['meta'] ?? null;
@@ -94,15 +94,15 @@ class MessagesController extends BaseApiController
public function store(MessageSendRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$this->authorize('create', Message::class);
try {
$message = $this->commandService->create($userId, $request->validated(), $request->file('attachment'));
$message = $this->commandService->create($guard, $request->validated(), $request->file('attachment'));
} catch (\Throwable $e) {
Log::error('Message send failed: ' . $e->getMessage());
return $this->error($e->getMessage(), Response::HTTP_BAD_REQUEST);
@@ -115,12 +115,12 @@ class MessagesController extends BaseApiController
public function receive(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$messages = $this->queryService->receivedAndMarkRead($userId);
$messages = $this->queryService->receivedAndMarkRead($guard);
return $this->success([
'messages' => (new MessageCollection(collect($messages)))->toArray(request()),
@@ -193,9 +193,9 @@ class MessagesController extends BaseApiController
private function listMessages(MessageIndexRequest $request, string $bucket): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$filters = $request->validated();
@@ -205,10 +205,10 @@ class MessagesController extends BaseApiController
$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),
'sent' => $this->queryService->sent($guard, $filters, $page, $perPage),
'drafts' => $this->queryService->drafts($guard, $filters, $page, $perPage),
'trash' => $this->queryService->trash($guard, $filters, $page, $perPage),
default => $this->queryService->inbox($guard, $filters, $page, $perPage),
};
$collection = new MessageCollection($paginator);
@@ -219,4 +219,17 @@ class MessagesController extends BaseApiController
'meta' => $meta,
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Messaging;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Whatsapp\WhatsappInviteSendRequest;
use App\Http\Requests\Whatsapp\WhatsappLinkIndexRequest;
use App\Http\Requests\Whatsapp\WhatsappLinkStoreRequest;
@@ -132,8 +132,12 @@ class WhatsappController extends BaseApiController
public function updateMembership(WhatsappMembershipUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
try {
$userId = (int) (auth()->id() ?? 0) ?: null;
$result = $this->membershipService->updateMembership($request->validated(), $userId);
} catch (\Throwable $e) {
Log::error('WhatsApp membership update failed: ' . $e->getMessage());
@@ -153,4 +157,17 @@ class WhatsappController extends BaseApiController
'term' => $result['term'] ?? [],
], 'Membership saved.');
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Notifications;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Notifications\NotificationActiveRequest;
use App\Http\Requests\Notifications\NotificationDeletedRequest;
use App\Http\Requests\Notifications\NotificationIndexRequest;
@@ -36,13 +36,13 @@ class NotificationController extends BaseApiController
public function index(NotificationIndexRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$result = $this->listService->listForUser($userId, $payload);
$result = $this->listService->listForUser($guard, $payload);
return response()->json([
'ok' => true,
@@ -53,12 +53,12 @@ class NotificationController extends BaseApiController
public function show(int $notificationId): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$row = $this->showService->getForUser($userId, $notificationId);
$row = $this->showService->getForUser($guard, $notificationId);
if (!$row) {
return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404);
}
@@ -71,11 +71,15 @@ class NotificationController extends BaseApiController
public function store(NotificationSendRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
try {
$result = $this->sendService->send($payload, $actorId);
$result = $this->sendService->send($payload, $guard);
} catch (\Throwable $e) {
Log::error('Notification send failed.', ['error' => $e->getMessage()]);
return response()->json(['ok' => false, 'message' => 'Failed to send notification.'], 500);
@@ -129,12 +133,12 @@ class NotificationController extends BaseApiController
public function markRead(int $notificationId): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$ok = $this->readService->markRead($userId, $notificationId);
$ok = $this->readService->markRead($guard, $notificationId);
if (!$ok) {
return response()->json(['ok' => false, 'message' => 'Notification not found.'], 404);
}
@@ -162,4 +166,17 @@ class NotificationController extends BaseApiController
'notifications' => $data['notifications'],
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -0,0 +1,111 @@
<?php
namespace App\Http\Controllers\Api\Parents;
use App\Http\Controllers\Controller;
use App\Services\Parents\AuthorizedUsersManagementService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Validator;
/**
* Public invite confirmation & password setup for authorized users.
*/
class AuthorizedUserInviteController extends Controller
{
public function __construct(
private AuthorizedUsersManagementService $svc,
) {}
public function confirm(Request $request): RedirectResponse|JsonResponse
{
$token = (string) ($request->query('token') ?? '');
try {
$result = $this->svc->confirmEmailClick($token);
} catch (\InvalidArgumentException $e) {
if ($request->expectsJson()) {
return response()->json(['message' => $e->getMessage()], 400);
}
return response(
'<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="font-family:sans-serif;padding:24px;">'
.'<p>'.e($e->getMessage()).'</p></body></html>',
400,
['Content-Type' => 'text/html; charset=UTF-8'],
);
}
if ($request->expectsJson()) {
return response()->json([
'message' => 'Confirmed.',
'redirect_url' => $result['redirect_url'],
]);
}
return redirect()->away($result['redirect_url']);
}
public function setPasswordForm(Request $request, int $authorizedUserId): JsonResponse|Response
{
$token = (string) ($request->query('token') ?? '');
$row = $this->svc->findActiveSetupRow($authorizedUserId, $token);
if (! $row) {
if ($request->expectsJson()) {
return response()->json(['message' => 'Invalid or expired confirmation link.'], 400);
}
return response('Invalid or expired confirmation link.', 400);
}
if ($request->expectsJson()) {
return response()->json([
'message' => 'OK',
'user_id' => $authorizedUserId,
'token' => $token,
]);
}
return response(
'<!DOCTYPE html><html><head><meta charset="utf-8"><title>Set password</title></head>'
.'<body><p>Set your password using the client app or API POST to this URL with JSON body.</p></body></html>',
200,
['Content-Type' => 'text/html; charset=UTF-8'],
);
}
public function savePassword(Request $request, int $authorizedUserId): JsonResponse
{
$validator = Validator::make($request->all(), [
'password' => ['required', 'string', 'min:8', 'regex:/[A-Z]/', 'regex:/[a-z]/', 'regex:/[0-9]/', 'regex:/[\W_]/'],
'password_confirm' => ['required', 'same:password'],
'user_id' => ['required', 'integer'],
'token' => ['required', 'string'],
], [
'password.regex' => 'Password must include uppercase, lowercase, number, and special character.',
]);
if ($validator->fails()) {
return response()->json([
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
}
$data = $validator->validated();
try {
$this->svc->setAuthorizedUserPassword(
$authorizedUserId,
(int) $data['user_id'],
(string) $data['token'],
(string) $data['password'],
);
} catch (\InvalidArgumentException $e) {
return response()->json(['message' => $e->getMessage()], 400);
}
return response()->json(['message' => 'Password has been successfully set.']);
}
}
@@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Api\Parents;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Parents\StoreAuthorizedUserRequest;
use App\Http\Requests\Parents\UpdateAuthorizedUserRequest;
use App\Models\AuthorizedUser;
use App\Services\Parents\AuthorizedUsersManagementService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
/**
* Parent CRUD for secondary authorized users on their account (JWT parent role).
*/
class AuthorizedUsersController extends BaseApiController
{
public function __construct(
private AuthorizedUsersManagementService $svc,
) {
parent::__construct();
}
public function index(): JsonResponse
{
$guard = $this->guardJsonOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$rows = AuthorizedUser::query()
->where('user_id', $guard)
->orderBy('id')
->get();
return $this->success($rows);
}
public function show(int $id): JsonResponse
{
$guard = $this->guardJsonOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$resolution = $this->authorizedUserForParent($id, $guard);
if ($resolution instanceof JsonResponse) {
return $resolution;
}
return $this->success($resolution);
}
public function store(StoreAuthorizedUserRequest $request): JsonResponse
{
$guard = $this->guardJsonOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$email = strtolower($request->validated('email'));
$result = $this->svc->inviteByEmail($guard, $email);
$message = 'Authorized user added. A confirmation email has been sent.';
if (! $result['created']) {
return $this->success(['message' => $message], $message, Response::HTTP_CREATED);
}
return $this->success(
['message' => $message, 'id' => $result['record']->id],
$message,
Response::HTTP_CREATED
);
}
public function update(UpdateAuthorizedUserRequest $request, int $id): JsonResponse
{
$guard = $this->guardJsonOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$resolution = $this->authorizedUserForParent($id, $guard);
if ($resolution instanceof JsonResponse) {
return $resolution;
}
/** @var AuthorizedUser $row */
$row = $resolution;
$email = $request->validated('email') ?? null;
if (is_string($email) && $email !== '') {
$row->email = strtolower($email);
}
$row->save();
return $this->success(['message' => 'Authorized user information updated.'], 'Authorized user information updated.');
}
public function destroy(int $id): JsonResponse
{
$guard = $this->guardJsonOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$resolution = $this->authorizedUserForParent($id, $guard);
if ($resolution instanceof JsonResponse) {
return $resolution;
}
$resolution->delete();
return $this->success(['message' => 'Authorized user deleted successfully.'], 'Authorized user deleted successfully.');
}
private function guardJsonOrUnauthorized(): int|JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return $this->error('Authentication required.', Response::HTTP_UNAUTHORIZED);
}
return $parentId;
}
private function authorizedUserForParent(int $id, int $parentId): AuthorizedUser|JsonResponse
{
$row = AuthorizedUser::query()->find($id);
if (! $row) {
return $this->error('Authorized user not found.', Response::HTTP_NOT_FOUND);
}
if ((int) $row->user_id !== $parentId) {
return $this->error('You do not have access to this resource.', Response::HTTP_FORBIDDEN);
}
return $row;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Parents;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Parents\ParentAttendanceReportSubmitRequest;
use App\Http\Requests\Parents\ParentAttendanceReportListRequest;
use App\Http\Requests\Parents\ParentAttendanceReportCheckRequest;
@@ -20,12 +20,12 @@ class ParentAttendanceReportController extends BaseApiController
public function form(): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->reportService->formData($parentId);
$data = $this->reportService->formData($guard);
return response()->json([
'ok' => true,
@@ -40,13 +40,13 @@ class ParentAttendanceReportController extends BaseApiController
public function submit(ParentAttendanceReportSubmitRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
$result = $this->reportService->submit($parentId, $request->validated());
$result = $this->reportService->submit($guard, $request->validated());
} catch (\RuntimeException $e) {
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
}
@@ -62,13 +62,18 @@ class ParentAttendanceReportController extends BaseApiController
public function list(ParentAttendanceReportListRequest $request): JsonResponse
{
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$start = $payload['start'] ?? now()->toDateString();
$end = $payload['end'] ?? null;
$schoolYear = $payload['school_year'] ?? null;
$semester = $payload['semester'] ?? null;
$rows = $this->reportService->listReports($start, $end, $schoolYear, $semester);
$rows = $this->reportService->listReports($start, $end, $schoolYear, $semester, $guard);
return response()->json([
'ok' => true,
@@ -78,8 +83,13 @@ class ParentAttendanceReportController extends BaseApiController
public function checkExisting(ParentAttendanceReportCheckRequest $request): JsonResponse
{
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
$rows = $this->reportService->checkExisting($request->validated());
$rows = $this->reportService->checkExisting($guard, $request->validated());
} catch (\RuntimeException $e) {
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
}
@@ -92,17 +102,30 @@ class ParentAttendanceReportController extends BaseApiController
public function update(ParentAttendanceReportUpdateRequest $request, int $reportId): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
$this->reportService->updateReport($parentId, $reportId, $request->validated());
$this->reportService->updateReport($guard, $reportId, $request->validated());
} catch (\RuntimeException $e) {
return response()->json(['ok' => false, 'message' => $e->getMessage()], 422);
}
return response()->json(['ok' => true]);
}
/**
* @return int|JsonResponse
*/
private function parentIdOrUnauthorized(): int|JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $parentId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Parents;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Parents\ParentAttendanceRequest;
use App\Http\Requests\Parents\ParentEnrollmentActionRequest;
use App\Http\Requests\Parents\ParentEnrollmentRequest;
@@ -41,12 +41,12 @@ class ParentController extends BaseApiController
public function attendance(ParentAttendanceRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->attendanceService->listAttendance($parentId, $request->validated()['school_year'] ?? null);
$data = $this->attendanceService->listAttendance($guard, $request->validated()['school_year'] ?? null);
return response()->json([
'ok' => true,
@@ -58,13 +58,13 @@ class ParentController extends BaseApiController
public function invoices(ParentInvoiceRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$schoolYear = $request->validated()['school_year'] ?? null;
$rows = $this->invoiceService->listInvoices($parentId, $schoolYear);
$rows = $this->invoiceService->listInvoices($guard, $schoolYear);
return response()->json([
'ok' => true,
@@ -74,12 +74,12 @@ class ParentController extends BaseApiController
public function enrollments(ParentEnrollmentRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->enrollmentService->overview($parentId, $request->validated()['school_year'] ?? null);
$data = $this->enrollmentService->overview($guard, $request->validated()['school_year'] ?? null);
return response()->json([
'ok' => true,
@@ -94,14 +94,14 @@ class ParentController extends BaseApiController
public function updateEnrollments(ParentEnrollmentActionRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$result = $this->enrollmentService->updateEnrollment(
$parentId,
$guard,
$payload['enroll'] ?? [],
$payload['withdraw'] ?? []
);
@@ -115,12 +115,12 @@ class ParentController extends BaseApiController
public function registration(): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->registrationService->overview($parentId);
$data = $this->registrationService->overview($guard);
return response()->json([
'ok' => true,
@@ -136,14 +136,14 @@ class ParentController extends BaseApiController
public function storeRegistration(ParentRegistrationRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$result = $this->registrationService->register(
$parentId,
$guard,
$payload['students'] ?? [],
$payload['emergency_contacts'] ?? []
);
@@ -156,36 +156,36 @@ class ParentController extends BaseApiController
public function updateStudent(ParentStudentUpdateRequest $request, int $studentId): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$this->registrationService->updateStudent($parentId, $studentId, $request->validated());
$this->registrationService->updateStudent($guard, $studentId, $request->validated());
return response()->json(['ok' => true]);
}
public function deleteStudent(int $studentId): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$this->registrationService->deleteStudent($parentId, $studentId);
$this->registrationService->deleteStudent($guard, $studentId);
return response()->json(['ok' => true]);
}
public function emergencyContacts(): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$rows = $this->emergencyContactService->list($parentId);
$rows = $this->emergencyContactService->list($guard);
return response()->json([
'ok' => true,
@@ -195,12 +195,12 @@ class ParentController extends BaseApiController
public function storeEmergencyContact(ParentEmergencyContactRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$contact = $this->emergencyContactService->store($parentId, $request->validated());
$contact = $this->emergencyContactService->store($guard, $request->validated());
return response()->json([
'ok' => true,
@@ -210,12 +210,12 @@ class ParentController extends BaseApiController
public function updateEmergencyContact(ParentEmergencyContactRequest $request, int $contactId): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$contact = $this->emergencyContactService->update($parentId, $contactId, $request->validated());
$contact = $this->emergencyContactService->update($guard, $contactId, $request->validated());
return response()->json([
'ok' => true,
@@ -225,12 +225,12 @@ class ParentController extends BaseApiController
public function profile(): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$profile = $this->profileService->getProfile($parentId);
$profile = $this->profileService->getProfile($guard);
return response()->json([
'ok' => true,
@@ -240,12 +240,12 @@ class ParentController extends BaseApiController
public function updateProfile(ParentProfileUpdateRequest $request): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$profile = $this->profileService->updateProfile($parentId, $request->validated());
$profile = $this->profileService->updateProfile($guard, $request->validated());
return response()->json([
'ok' => true,
@@ -255,12 +255,12 @@ class ParentController extends BaseApiController
public function events(): JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->eventService->overview($parentId);
$data = $this->eventService->overview($guard);
return response()->json([
'ok' => true,
@@ -271,14 +271,27 @@ class ParentController extends BaseApiController
}
public function updateParticipation(ParentEventParticipationRequest $request): JsonResponse
{
$guard = $this->parentIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$this->eventService->updateParticipation($guard, $request->validated()['participation'] ?? []);
return response()->json(['ok' => true]);
}
/**
* @return int|JsonResponse Authenticated parent user id, or 401 JSON for this controller's legacy `{ ok }` shape.
*/
private function parentIdOrUnauthorized(): int|JsonResponse
{
$parentId = (int) (auth()->id() ?? 0);
if ($parentId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
$this->eventService->updateParticipation($parentId, $request->validated()['participation'] ?? []);
return response()->json(['ok' => true]);
return $parentId;
}
}
@@ -0,0 +1,282 @@
<?php
namespace App\Http\Controllers\Api\PrintRequests;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Models\PrintRequest;
use App\Models\User;
use App\Services\PrintRequests\PrintRequestsPortalService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
class PrintRequestsController extends BaseApiController
{
public function __construct(
private PrintRequestsPortalService $portal,
) {
parent::__construct();
}
public function teacher(): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $this->success($this->portal->teacherBootstrap($uid));
}
public function admin(): JsonResponse
{
return $this->success([
'print_requests' => $this->portal->adminPrintRequests(),
]);
}
public function store(Request $request): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$validator = Validator::make($request->all(), [
'file' => ['required', 'file', 'max:5120', 'mimes:pdf,jpg,jpeg,png,doc,docx,txt'],
'page_selection' => ['nullable', 'string', 'regex:/^\s*\d+(?:\s*-\s*\d+)?(?:\s*,\s*\d+(?:\s*-\s*\d+)?)*\s*$/'],
'num_copies' => ['required', 'integer', 'min:1'],
'required_by' => ['required', 'date'],
'pickup_method' => ['required', 'string', 'max:255'],
'class_id' => ['required', 'integer', 'min:1'],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
/** @var \Illuminate\Http\UploadedFile $file */
$file = $request->file('file');
$model = $this->portal->storeTeacherUpload($validator->validated(), $file, $uid);
return $this->respondCreated(
['print_request' => $this->portal->normalizePrintRequestRow($model->toArray())],
'Print request created successfully.'
);
}
public function teacherUpdate(Request $request, int $id): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$pr = PrintRequest::query()->find($id);
if (! $pr) {
return $this->error('Print request not found.', Response::HTTP_NOT_FOUND);
}
if (! $this->portal->teacherOwnsRequest($pr, $uid)) {
return $this->error('You are not authorized to edit this request.', Response::HTTP_FORBIDDEN);
}
if (! $this->portal->teacherMayEditOrDelete($pr)) {
return $this->error('Cannot edit a request that is already being processed.', Response::HTTP_BAD_REQUEST);
}
$rules = [
'num_copies' => ['required', 'integer', 'min:1'],
'required_by' => ['required', 'date'],
'pickup_method' => ['required', 'string', Rule::in(['Self Pickup', 'Delivered to class'])],
'page_selection' => ['nullable', 'string', 'regex:/^\s*\d+(?:\s*-\s*\d+)?(?:\s*,\s*\d+(?:\s*-\s*\d+)?)*\s*$/'],
];
if ($request->hasFile('file') && $request->file('file')->isValid()) {
$rules['file'] = ['required', 'file', 'max:5120', 'mimes:pdf,jpg,jpeg,png,doc,docx,txt'];
}
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
$data = $validator->validated();
$newFile = ($request->hasFile('file') && $request->file('file')->isValid())
? $request->file('file')
: null;
$model = $this->portal->updateTeacherFields($pr, $data, $newFile);
return $this->success(
['print_request' => $this->portal->normalizePrintRequestRow($model->toArray())],
'Print request updated successfully.'
);
}
public function adminStatus(Request $request, int $id): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$validator = Validator::make($request->all(), [
'status' => ['required', 'string', Rule::in(['not_assigned', 'assigned', 'done', 'delivered'])],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
$pr = PrintRequest::query()->find($id);
if (! $pr) {
return $this->error('Print request not found.', Response::HTTP_NOT_FOUND);
}
$newStatus = (string) $validator->validated()['status'];
try {
$model = $this->portal->applyAdminStatus($pr, $newStatus, $uid);
} catch (\InvalidArgumentException $e) {
return $this->error($e->getMessage(), Response::HTTP_UNPROCESSABLE_ENTITY);
}
return $this->success(
['print_request' => $this->portal->normalizePrintRequestRow($model->toArray())],
'Status updated successfully.'
);
}
public function destroy(int $id): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$pr = PrintRequest::query()->find($id);
if (! $pr) {
return $this->error('Print request not found.', Response::HTTP_NOT_FOUND);
}
if (! $this->portal->teacherOwnsRequest($pr, $uid)) {
return $this->error('You are not authorized to delete this request.', Response::HTTP_FORBIDDEN);
}
if (! $this->portal->teacherMayEditOrDelete($pr)) {
return $this->error('Cannot delete a request that is already being processed.', Response::HTTP_BAD_REQUEST);
}
$this->portal->deleteTeacherRequest($pr);
return $this->respondDeleted(null, 'Print request deleted successfully.');
}
public function copy(int $id): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$pr = PrintRequest::query()->find($id);
if (! $pr) {
return $this->error('Print request not found.', Response::HTTP_NOT_FOUND);
}
if (! $this->portal->teacherOwnsRequest($pr, $uid)) {
return $this->error('You are not authorized to copy this request.', Response::HTTP_FORBIDDEN);
}
try {
$model = $this->portal->copyFromExisting($pr, $uid);
} catch (\InvalidArgumentException $e) {
return $this->error($e->getMessage(), Response::HTTP_BAD_REQUEST);
}
return $this->respondCreated(
['print_request' => $this->portal->normalizePrintRequestRow($model->toArray())],
'Print request copied successfully.'
);
}
public function handCopy(Request $request): JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$validator = Validator::make($request->all(), [
'num_copies' => ['required', 'integer', 'min:1'],
'required_by' => ['required', 'date'],
'pickup_method' => ['required', 'string', 'max:255'],
'class_id' => ['required', 'integer', 'min:1'],
]);
if ($validator->fails()) {
return $this->respondValidationError($validator->errors()->toArray());
}
$model = $this->portal->storeHandCopy($validator->validated(), $uid);
return $this->respondCreated(
['print_request' => $this->portal->normalizePrintRequestRow($model->toArray())],
'Copy request submitted. Please hand the original document to the copy center.'
);
}
public function download(int $id): BinaryFileResponse|JsonResponse
{
$uid = $this->getCurrentUserId();
if (! $uid) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
$pr = PrintRequest::query()->find($id);
if (! $pr) {
return $this->error('Print request not found.', Response::HTTP_NOT_FOUND);
}
$isAdmin = $this->userHasPrintAdminRole(Auth::user());
if (! $this->portal->authorizeDownload($pr, $uid, $isAdmin)) {
return $this->error('Forbidden.', Response::HTTP_FORBIDDEN);
}
$path = $this->portal->absolutePathForDownload($pr);
if ($path === null || ! is_readable($path)) {
return $this->error('File not found.', Response::HTTP_NOT_FOUND);
}
$mime = @mime_content_type($path) ?: 'application/octet-stream';
$safe = basename($path);
return response()->download($path, $safe, [
'Content-Type' => $mime,
]);
}
private function userHasPrintAdminRole(?User $user): bool
{
if (! $user) {
return false;
}
$roles = $user->roles()
->pluck('roles.name')
->map(fn ($name) => strtolower((string) $name))
->toArray();
foreach (['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'] as $allowed) {
if (in_array($allowed, $roles, true)) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Api\Public;
use App\Http\Controllers\Controller;
use App\Services\PublicWinners\PublicWinnersPortalService;
use Illuminate\Http\JsonResponse;
/**
* Published competition winners (public JSON).
*/
class PublicWinnersController extends Controller
{
public function __construct(
private PublicWinnersPortalService $winners,
) {}
public function competitionIndex(): JsonResponse
{
return response()->json([
'status' => true,
'data' => [
'competitions' => $this->winners->publishedCompetitions(),
],
]);
}
public function competitionShow(int $id): JsonResponse
{
$payload = $this->winners->competitionPayload($id);
if ($payload === null) {
return response()->json([
'status' => false,
'message' => 'Competition not found.',
], 404);
}
return response()->json([
'status' => true,
'data' => $payload,
]);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Reports;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Files\FileNameRequest;
use App\Http\Resources\Files\FileMetaResource;
use App\Services\Files\ExamDraftDownloadNameService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Reports;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Reports\ReportCards\ReportCardAcknowledgementRequest;
use App\Http\Requests\Reports\ReportCards\ReportCardCompletenessRequest;
use App\Http\Requests\Reports\ReportCards\ReportCardMetaRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Reports;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Reports\SlipLogListRequest;
use App\Http\Requests\Reports\SlipPreviewRequest;
use App\Http\Requests\Reports\SlipPrintRequest;
@@ -20,8 +20,12 @@ class SlipPrinterController extends BaseApiController
public function print(SlipPrintRequest $request)
{
$userId = (int) (auth()->id() ?? 0);
$result = $this->service->print($request->validated(), $userId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->service->print($request->validated(), $guard);
if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Print failed.'], 422);
@@ -35,8 +39,12 @@ class SlipPrinterController extends BaseApiController
public function preview(SlipPreviewRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
$result = $this->service->preview($request->validated(), $userId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->service->preview($request->validated(), $guard);
return response()->json([
'ok' => true,
@@ -47,6 +55,11 @@ class SlipPrinterController extends BaseApiController
public function logs(SlipLogListRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$rows = $this->service->logs($payload['school_year'] ?? null, $payload['semester'] ?? null);
@@ -58,8 +71,12 @@ class SlipPrinterController extends BaseApiController
public function reprint(SlipReprintRequest $request)
{
$userId = (int) (auth()->id() ?? 0);
$result = $this->service->reprint((int) $request->validated()['id'], $userId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->service->reprint((int) $request->validated()['id'], $guard);
if (!$result['ok']) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Slip not found.'], 404);
@@ -70,4 +87,17 @@ class SlipPrinterController extends BaseApiController
'Content-Disposition' => 'inline; filename="' . $result['filename'] . '"',
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Reports;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Reports\Stickers\StickerFormDataRequest;
use App\Http\Requests\Reports\Stickers\StickerPrintRequest;
use App\Http\Requests\Reports\Stickers\StickerStudentsByClassRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student;
@@ -52,6 +52,11 @@ class FinalController extends BaseApiController
public function update(ScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->service->update(
'final_exam',
@@ -60,10 +65,10 @@ class FinalController extends BaseApiController
(string) $payload['school_year'],
$payload['scores'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year'], $userId);
return response()->json(['ok' => true, 'updated' => $count]);
}
@@ -93,9 +98,9 @@ class FinalController extends BaseApiController
]);
}
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear, int $actorUserId): void
{
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $actorUserId);
if (empty($studentInfo)) {
return;
}
@@ -104,4 +109,17 @@ class FinalController extends BaseApiController
} catch (\Throwable $e) {
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreAddColumnRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource;
@@ -58,6 +58,11 @@ class HomeworkController extends BaseApiController
public function update(ScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->service->update(
(int) $payload['class_section_id'],
@@ -65,22 +70,27 @@ class HomeworkController extends BaseApiController
(string) $payload['school_year'],
$payload['scores'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year'], $userId);
return response()->json(['ok' => true, 'updated' => $count]);
}
public function addColumn(ScoreAddColumnRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$nextIndex = $this->service->addColumn(
(int) $payload['class_section_id'],
(string) $payload['semester'],
(string) $payload['school_year'],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true, 'homework_index' => $nextIndex]);
@@ -111,9 +121,9 @@ class HomeworkController extends BaseApiController
]);
}
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear, int $actorUserId): void
{
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $actorUserId);
if (empty($studentInfo)) {
return;
}
@@ -122,4 +132,17 @@ class HomeworkController extends BaseApiController
} catch (\Throwable $e) {
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student;
@@ -52,6 +52,11 @@ class MidtermController extends BaseApiController
public function update(ScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->service->update(
'midterm_exam',
@@ -60,10 +65,10 @@ class MidtermController extends BaseApiController
(string) $payload['school_year'],
$payload['scores'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year'], $userId);
return response()->json(['ok' => true, 'updated' => $count]);
}
@@ -93,9 +98,9 @@ class MidtermController extends BaseApiController
]);
}
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear, int $actorUserId): void
{
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $actorUserId);
if (empty($studentInfo)) {
return;
}
@@ -104,4 +109,17 @@ class MidtermController extends BaseApiController
} catch (\Throwable $e) {
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource;
use App\Models\Student;
@@ -56,6 +56,11 @@ class ParticipationController extends BaseApiController
public function update(ScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->service->update(
(int) $payload['class_section_id'],
@@ -63,10 +68,10 @@ class ParticipationController extends BaseApiController
(string) $payload['school_year'],
$payload['scores'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year'], $userId);
return response()->json(['ok' => true, 'updated' => $count]);
}
@@ -96,9 +101,9 @@ class ParticipationController extends BaseApiController
]);
}
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear, int $actorUserId): void
{
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $actorUserId);
if (empty($studentInfo)) {
return;
}
@@ -107,4 +112,17 @@ class ParticipationController extends BaseApiController
} catch (\Throwable $e) {
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreAddColumnRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource;
@@ -58,6 +58,11 @@ class ProjectController extends BaseApiController
public function update(ScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->service->update(
(int) $payload['class_section_id'],
@@ -65,22 +70,27 @@ class ProjectController extends BaseApiController
(string) $payload['school_year'],
$payload['scores'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year'], $userId);
return response()->json(['ok' => true, 'updated' => $count]);
}
public function addColumn(ScoreAddColumnRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$nextIndex = $this->service->addColumn(
(int) $payload['class_section_id'],
(string) $payload['semester'],
(string) $payload['school_year'],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true, 'project_index' => $nextIndex]);
@@ -111,9 +121,9 @@ class ProjectController extends BaseApiController
]);
}
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear, int $actorUserId): void
{
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $actorUserId);
if (empty($studentInfo)) {
return;
}
@@ -123,4 +133,17 @@ class ProjectController extends BaseApiController
// swallow errors to keep API response stable
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreAddColumnRequest;
use App\Http\Requests\Scores\ScoreUpdateRequest;
use App\Http\Resources\Scores\ScoreStudentResource;
@@ -58,6 +58,11 @@ class QuizController extends BaseApiController
public function update(ScoreUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$count = $this->service->update(
(int) $payload['class_section_id'],
@@ -65,22 +70,27 @@ class QuizController extends BaseApiController
(string) $payload['school_year'],
$payload['scores'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year']);
$this->refreshSemesterScores((int) $payload['class_section_id'], (string) $payload['semester'], (string) $payload['school_year'], $userId);
return response()->json(['ok' => true, 'updated' => $count]);
}
public function addColumn(ScoreAddColumnRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$nextIndex = $this->service->addColumn(
(int) $payload['class_section_id'],
(string) $payload['semester'],
(string) $payload['school_year'],
(int) (auth()->id() ?? 0)
$userId
);
return response()->json(['ok' => true, 'quiz_index' => $nextIndex]);
@@ -111,9 +121,9 @@ class QuizController extends BaseApiController
]);
}
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear): void
private function refreshSemesterScores(int $classSectionId, string $semester, string $schoolYear, int $actorUserId): void
{
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, (int) (auth()->id() ?? 0));
$studentInfo = Student::getStudentInfoByClassSectionId($classSectionId, $semester, $schoolYear, $actorUserId);
if (empty($studentInfo)) {
return;
}
@@ -122,4 +132,17 @@ class QuizController extends BaseApiController
} catch (\Throwable $e) {
}
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreCommentListRequest;
use App\Http\Requests\Scores\ScoreCommentSaveRequest;
use App\Http\Requests\Scores\ScoreCommentUpdateRequest;
@@ -72,6 +72,11 @@ class ScoreCommentController extends BaseApiController
public function store(ScoreCommentSaveRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$result = $this->service->save(
(int) $payload['class_section_id'],
@@ -79,7 +84,7 @@ class ScoreCommentController extends BaseApiController
(string) $payload['school_year'],
$payload['comments'],
$payload['missing_ok'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
if (!empty($result['errors'])) {
@@ -91,6 +96,11 @@ class ScoreCommentController extends BaseApiController
public function update(ScoreCommentUpdateRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$result = $this->service->update(
(int) $payload['class_section_id'],
@@ -98,7 +108,7 @@ class ScoreCommentController extends BaseApiController
(string) $payload['school_year'],
$payload['comments'] ?? [],
$payload['reviews'] ?? [],
(int) (auth()->id() ?? 0)
$userId
);
if (!empty($result['errors'])) {
@@ -107,4 +117,17 @@ class ScoreCommentController extends BaseApiController
return response()->json(['ok' => true]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScoreLockRequest;
use App\Http\Requests\Scores\ScoreOverviewRequest;
use App\Http\Requests\Scores\ScoreStudentRequest;
@@ -19,8 +19,12 @@ class ScoreController extends BaseApiController
public function overview(ScoreOverviewRequest $request): JsonResponse
{
$teacherId = $this->authenticatedUserIdOrUnauthorized();
if ($teacherId instanceof JsonResponse) {
return $teacherId;
}
$payload = $request->validated();
$teacherId = (int) ($request->user()?->id ?? (auth()->id() ?? 0));
$classSectionId = $payload['class_section_id'] ?? $request->input('class_section_id');
$semester = $payload['semester'] ?? $request->input('semester');
$schoolYear = $payload['school_year'] ?? $request->input('school_year');
@@ -45,13 +49,18 @@ class ScoreController extends BaseApiController
public function lock(ScoreLockRequest $request): JsonResponse
{
$userId = $this->authenticatedUserIdOrUnauthorized();
if ($userId instanceof JsonResponse) {
return $userId;
}
$payload = $request->validated();
$this->service->submitScoresLock(
(int) $payload['class_section_id'],
(string) $payload['semester'],
(string) $payload['school_year'],
$payload['missing_ok'] ?? [],
(int) ($request->user()?->id ?? (auth()->id() ?? 0))
$userId
);
return response()->json(['ok' => true]);
@@ -59,11 +68,28 @@ class ScoreController extends BaseApiController
public function studentScores(ScoreStudentRequest $request): JsonResponse
{
$parentId = $this->authenticatedUserIdOrUnauthorized();
if ($parentId instanceof JsonResponse) {
return $parentId;
}
$payload = $request->validated();
$parentId = (int) ($request->user()?->id ?? (auth()->id() ?? 0));
$data = $this->service->viewStudentScores($parentId, (string) $payload['school_year']);
return response()->json(['ok' => true] + $data);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (request()->user()?->id ?? auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Scores;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Scores\ScorePredictorRequest;
use App\Http\Resources\Scores\ScorePredictorStudentResource;
use App\Services\Scores\ScorePredictorService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Settings\ConfigurationStoreRequest;
use App\Http\Requests\Settings\ConfigurationUpdateRequest;
use App\Http\Resources\Settings\ConfigurationResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Events\EventChargeIndexRequest;
use App\Http\Requests\Events\EventChargeUpdateRequest;
use App\Http\Requests\Events\EventIndexRequest;
@@ -62,11 +62,15 @@ class EventController extends BaseApiController
public function store(EventStoreRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
try {
$result = $this->managementService->create($payload, $request->file('flyer'), $actorId);
$result = $this->managementService->create($payload, $request->file('flyer'), $guard);
} catch (\Throwable $e) {
Log::error('Event creation failed.', ['error' => $e->getMessage()]);
return response()->json(['ok' => false, 'message' => 'Failed to create event.'], 500);
@@ -100,8 +104,12 @@ class EventController extends BaseApiController
public function destroy(int $eventId): JsonResponse
{
$actorId = (int) (auth()->id() ?? 0);
$result = $this->managementService->delete($eventId, $actorId);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->managementService->delete($eventId, $guard);
if (empty($result['ok'])) {
return response()->json(['ok' => false, 'message' => $result['message'] ?? 'Failed to delete event.'], 404);
@@ -124,8 +132,12 @@ class EventController extends BaseApiController
public function updateCharges(EventChargeUpdateRequest $request, int $eventId): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $request->validated();
$actorId = (int) (auth()->id() ?? 0);
$result = $this->chargeService->updateParticipation(
(int) $payload['parent_id'],
@@ -133,7 +145,7 @@ class EventController extends BaseApiController
$payload['participation'],
(string) $payload['school_year'],
(string) $payload['semester'],
$actorId
$guard
);
if (empty($result['ok'])) {
@@ -163,4 +175,17 @@ class EventController extends BaseApiController
'students' => EventStudentChargeResource::collection($students),
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Settings\PolicyShowRequest;
use App\Http\Resources\Settings\PolicyResource;
use App\Services\Policy\PolicyContentService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Preferences\PreferencesIndexRequest;
use App\Http\Requests\Preferences\PreferencesUpsertRequest;
use App\Http\Resources\Preferences\PreferencesCollection;
@@ -42,12 +42,12 @@ class PreferencesController extends BaseApiController
public function show(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$payload = $this->queryService->getForUser($userId);
$payload = $this->queryService->getForUser($guard);
return $this->success([
'preferences' => new PreferencesResource($payload['preferences']),
@@ -74,19 +74,19 @@ class PreferencesController extends BaseApiController
public function store(PreferencesUpsertRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
$saved = $this->commandService->upsert($userId, $request->validated());
$saved = $this->commandService->upsert($guard, $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);
$payload = $this->queryService->getForUser($guard);
return $this->success([
'preferences' => new PreferencesResource($payload['preferences']),
@@ -136,4 +136,17 @@ class PreferencesController extends BaseApiController
return $this->success(null, 'Preferences deleted.');
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Settings\SchoolCalendarIndexRequest;
use App\Http\Requests\Settings\SchoolCalendarOptionsRequest;
use App\Http\Requests\Settings\SchoolCalendarStoreRequest;
@@ -61,7 +61,14 @@ class SchoolCalendarController extends BaseApiController
})->all();
if ($includeMeetings) {
$parentUserId = $audience === 'parent' ? (int) (auth()->id() ?? 0) : null;
$parentUserId = null;
if ($audience === 'parent') {
$actor = $this->authenticatedUserIdOrUnauthorized();
if ($actor instanceof JsonResponse) {
return $actor;
}
$parentUserId = $actor;
}
$meetingRows = $this->meetingService->listMeetings($filters['school_year'] ?? '', $audience, $parentUserId);
foreach ($meetingRows as $row) {
$formatted[] = $this->formatterService->formatMeetingEvent($row, $audience);
@@ -151,4 +158,17 @@ class SchoolCalendarController extends BaseApiController
'admins' => !empty($payload['send_email_admin']),
];
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Settings;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Settings\SettingsUpdateRequest;
use App\Http\Resources\Settings\SettingsResource;
use App\Models\Setting;
@@ -32,13 +32,13 @@ class SettingsController extends BaseApiController
$settings = Setting::singleton();
$this->authorize('update', $settings);
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
$updated = $this->settingsService->update($request->validated(), $userId);
$updated = $this->settingsService->update($request->validated(), $guard);
} catch (\Throwable $e) {
Log::error('Settings update failed: ' . $e->getMessage());
return $this->error('Unable to update settings.', Response::HTTP_INTERNAL_SERVER_ERROR);
@@ -48,4 +48,17 @@ class SettingsController extends BaseApiController
'settings' => new SettingsResource($updated),
], 'Settings updated.');
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Staff;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Staff\StaffIndexRequest;
use App\Http\Requests\Staff\StaffStoreRequest;
use App\Http\Requests\Staff\StaffUpdateRequest;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Staff;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Teachers\TeacherAbsenceSubmitRequest;
use App\Http\Requests\Teachers\TeacherClassViewRequest;
use App\Http\Requests\Teachers\TeacherSwitchClassRequest;
@@ -25,21 +25,21 @@ class TeacherController extends BaseApiController
public function classes(TeacherClassViewRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
try {
$data = $this->dashboardService->classView(
$userId,
$guard,
$request->validated()['class_section_id'] ?? null
);
} catch (\RuntimeException $e) {
return response()->json(['ok' => false, 'message' => $e->getMessage()], 403);
}
$teacher = $this->teacherPayload($data['teacher'] ?? null, $userId);
$teacher = $this->teacherPayload($data['teacher'] ?? null, $guard);
$studentsBySection = [];
foreach (($data['studentsBySection'] ?? []) as $sectionId => $students) {
@@ -59,15 +59,15 @@ class TeacherController extends BaseApiController
public function switchClass(TeacherSwitchClassRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$requested = (int) $request->validated()['class_section_id'];
try {
$data = $this->dashboardService->classView($userId, $requested);
$data = $this->dashboardService->classView($guard, $requested);
} catch (\RuntimeException $e) {
return response()->json(['ok' => false, 'message' => $e->getMessage()], 403);
}
@@ -87,12 +87,12 @@ class TeacherController extends BaseApiController
public function absenceForm(): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$data = $this->absenceService->formData($userId);
$data = $this->absenceService->formData($guard);
return response()->json([
'ok' => true,
@@ -106,12 +106,12 @@ class TeacherController extends BaseApiController
public function submitAbsence(TeacherAbsenceSubmitRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$result = $this->absenceService->submit($userId, $request->validated());
$result = $this->absenceService->submit($guard, $request->validated());
return response()->json([
'ok' => (bool) ($result['ok'] ?? false),
@@ -140,4 +140,17 @@ class TeacherController extends BaseApiController
'role' => User::getUserRoleName($userId),
];
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return response()->json(['ok' => false, 'message' => 'Unauthorized.'], 401);
}
return $userId;
}
}
@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api\Staff;
use App\Http\Controllers\Controller;
use App\Services\Email\EmailDispatchService;
use App\Services\Staff\StaffTimeOffLinkService;
use Illuminate\Http\Response;
/**
* Public principal acknowledgment link — sends courtesy confirmation to the requester.
*/
class TimeOffNotificationController extends Controller
{
public function __construct(
private StaffTimeOffLinkService $tokens,
private EmailDispatchService $mail,
) {}
public function notify(string $token): Response
{
$payload = $this->tokens->parseToken($token);
if (! $payload) {
return $this->respondHtml('Sorry, this link is invalid or has expired.', 400);
}
$email = trim((string) ($payload['email'] ?? ''));
$fullName = trim((string) ($payload['name'] ?? ''));
if ($email === '') {
return $this->respondHtml('Unable to notify the requester because their email was not included.', 400);
}
$dates = (string) ($payload['dates'] ?? '-');
$role = (string) ($payload['role'] ?? 'staff');
$reason = (string) ($payload['reason'] ?? '');
$reasonType = (string) ($payload['reason_type'] ?? '');
$submittedAt = (string) ($payload['submitted_at'] ?? '');
$origin = (string) ($payload['origin'] ?? 'staff portal');
try {
$subject = 'TimeOff Request - Principal Acknowledgment';
$body = '<div style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.5;">'
.'<p>Dear '.e($fullName !== '' ? $fullName : 'Staff Member').',</p>'
.'<p>This is a courtesy confirmation that your time-off request submitted via the '
.e($origin).' has been reviewed by the principal.</p>'
.'<table cellpadding="6" cellspacing="0" style="border-collapse:collapse;">'
.'<tr><td><strong>Role</strong></td><td>'.e($role).'</td></tr>'
.'<tr><td><strong>Dates</strong></td><td>'.e($dates !== '' ? $dates : '-').'</td></tr>'
.'<tr><td><strong>Reason Type</strong></td><td>'.e($reasonType !== '' ? $reasonType : '-').'</td></tr>'
.'<tr><td><strong>Reason</strong></td><td>'.e($reason !== '' ? $reason : '-').'</td></tr>'
.($submittedAt !== '' ? '<tr><td><strong>Submitted At</strong></td><td>'.e($submittedAt).'</td></tr>' : '')
.'</table>'
.'<p>If you have any follow-up questions, please contact the principal\'s office directly.</p>'
.'<p style="margin-top:16px;">Thank you,<br>Al Rahma School Administration</p>'
.'</div>';
$this->mail->send($email, $subject, $body, 'notifications');
} catch (\Throwable $e) {
logger()->error('Failed to send TimeOff confirmation to requester: '.$e->getMessage());
return $this->respondHtml(
'Something went wrong while sending the confirmation email. Please contact IT for help.',
500
);
}
return $this->respondHtml('Confirmation email sent to the requester. You may close this window.');
}
private function respondHtml(string $message, int $statusCode = 200): Response
{
$html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>TimeOff</title></head>'
.'<body style="font-family:Arial,Helvetica,sans-serif;padding:24px;">'
.'<p>'.e($message).'</p>'
.'</body></html>';
return response($html, $statusCode, [
'Content-Type' => 'text/html; charset=UTF-8',
]);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Students;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Students\StudentAssignClassRequest;
use App\Http\Requests\Students\StudentAssignmentListRequest;
use App\Http\Requests\Students\StudentAutoDistributeRequest;
@@ -676,10 +676,10 @@ class StudentController extends BaseApiController
return response()->json(['ok' => true, 'row' => $contact]);
}
public function updateRfid(Request $request, int $studentId): JsonResponse
public function updateBadgeScan(Request $request, int $studentId): JsonResponse
{
$validator = Validator::make($request->all(), [
'rfid_tag' => ['required', 'string', 'max:100'],
'badge_scan' => ['required', 'string', 'max:100'],
]);
if ($validator->fails()) {
@@ -694,7 +694,7 @@ class StudentController extends BaseApiController
return response()->json(['ok' => false, 'message' => 'Student not found.'], 404);
}
$student->update(['rfid_tag' => $validator->validated()['rfid_tag']]);
$student->update(['rfid_tag' => $validator->validated()['badge_scan']]);
return response()->json(['ok' => true]);
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Subjects;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Subjects\SubjectCurriculumStoreRequest;
use App\Http\Requests\Subjects\SubjectCurriculumUpdateRequest;
use App\Http\Resources\Subjects\SubjectClassResource;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Support;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Support\ContactSendRequest;
use App\Http\Resources\Support\ContactMessageResource;
use App\Services\Support\ContactMessageService;
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\Support;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\Support\SupportRequestIndexRequest;
use App\Http\Requests\Support\SupportRequestStoreRequest;
use App\Http\Resources\Support\SupportRequestCollection;
@@ -22,9 +22,9 @@ class SupportController extends BaseApiController
public function index(SupportRequestIndexRequest $request): JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$this->authorize('viewAny', SupportRequest::class);
@@ -39,7 +39,7 @@ class SupportController extends BaseApiController
->intersect(['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'])
->isNotEmpty();
$paginator = $this->supportService->list($userId, $filters, $page, $perPage, $isAdmin);
$paginator = $this->supportService->list($guard, $filters, $page, $perPage, $isAdmin);
$collection = new SupportRequestCollection($paginator);
return $this->success([
@@ -50,8 +50,13 @@ class SupportController extends BaseApiController
public function store(SupportRequestStoreRequest $request): JsonResponse
{
$guard = $this->authenticatedUserIdOrUnauthorized();
if ($guard instanceof JsonResponse) {
return $guard;
}
$user = auth()->user();
if (!$user) {
if (! $user) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
@@ -62,7 +67,7 @@ class SupportController extends BaseApiController
$payload['user_name'] = trim(($user->firstname ?? '') . ' ' . ($user->lastname ?? ''));
try {
$result = $this->supportService->create($user->id, $payload);
$result = $this->supportService->create((int) $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);
@@ -80,4 +85,17 @@ class SupportController extends BaseApiController
'page' => 'teacher_support',
]);
}
/**
* @return int|JsonResponse
*/
private function authenticatedUserIdOrUnauthorized(): int|JsonResponse
{
$userId = (int) (auth()->id() ?? 0);
if ($userId <= 0) {
return $this->error('Unauthorized.', Response::HTTP_UNAUTHORIZED);
}
return $userId;
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
class AccessDeniedController extends Controller
{
/** CI route: GET /access_denied */
public function accessDenied(): JsonResponse
{
return response()->json([
'status' => false,
'error' => 'forbidden',
'message' => 'Access denied.',
], 403);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Resources\System\DashboardRouteResource;
use App\Services\Dashboard\DashboardRouteService;
use Illuminate\Http\JsonResponse;
@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Controller;
use App\Services\ApplicationUrlService;
use App\Services\Dashboard\DashboardRouteService;
use Illuminate\Http\RedirectResponse;
/**
* Redirect /dashboard by role priority (web session).
*/
class DashboardRedirectController extends Controller
{
public function __construct(
private DashboardRouteService $service,
private ApplicationUrlService $urls,
) {
}
public function dashboard(): RedirectResponse
{
$user = auth()->user();
if (!$user) {
return redirect()->guest($this->urls->webLoginUrl());
}
$payload = $this->service->resolveForUser($user);
return redirect()->to($payload['route']);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Services\System\DatabaseHealthService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@@ -2,8 +2,7 @@
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Resources\System\HealthStatusResource;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Services\System\HealthCheckService;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
@@ -20,8 +19,7 @@ class HealthController extends BaseApiController
$payload = $this->service->check();
$status = $payload['ok'] ? Response::HTTP_OK : Response::HTTP_SERVICE_UNAVAILABLE;
return $this->success([
'health' => new HealthStatusResource($payload),
], 'Health check.', $status);
// CodeIgniter admin/API health returns a flat JSON body (no Laravel success envelope).
return response()->json($payload, $status);
}
}
@@ -2,7 +2,7 @@
namespace App\Http\Controllers\Api\System;
use App\Http\Controllers\Api\BaseApiController;
use App\Http\Controllers\Api\Core\BaseApiController;
use App\Http\Requests\System\NavItemReorderRequest;
use App\Http\Requests\System\NavItemStoreRequest;
use App\Http\Resources\System\NavBuilderDataResource;

Some files were not shown because too many files have changed in this diff Show More