# FDRS Codebase Audit — April 2026

**Date:** 2026-04-28 (initial audit) · 2026-04-29 (post-implementation waves 2 + 3)
**Scope:** Full audit of the Federal Departmental Recruitment System (Laravel-based recruitment + online exam platform).
**Companion docs:** [PERFORMANCE_AUDIT.md](../PERFORMANCE_AUDIT.md), [DEPLOY.md](../DEPLOY.md), [README.md](../README.md).

---

## Executive summary

The system has a remarkably consistent core (well-modeled domain, mature audit log, solid anti-cheat, well-structured Profile Wizard) but was held back by:

1. **Two design systems running side by side** — modern "Electric Ocean" theme on most pages vs. older Bootstrap+`--gov-navy` theme on form/edit pages.
2. **Missing communication subsystem** — only one outbound email (exam result); no notifications on profile/application approvals or rejections.
3. **Several security gaps** — exam password stored plaintext, no MFA on admin/super-admin accounts, macro-bearing Word docs accepted as supporting documents, no CAPTCHA, eligibility check too shallow.
4. **Operational rough edges** — N+1-style roll number generation, dashboards running 12-18 COUNT queries per render, no bulk-approve, no CSV question import.
5. **Zero domain test coverage.**

**Status (2026-04-29):** Options A (8 critical fixes), B (10 quick wins), C (design migration of 9 views), D (this doc), E (4 follow-on items: in-app notifications, user/role management UI, domain tests, Breeze cleanup), and F (4 hardening items: report date-range/CSV export, approval & exam-submission feature tests, CAPTCHA on register + forgot-password, security-headers middleware) **completed**. Remaining items (Medium/Low priority) catalogued at the bottom of this doc.

---

## 1. System overview

### Tech stack
- **Backend:** Laravel (PHP 8.3+), MySQL 8 / SQLite.
- **Frontend:** Blade + Bootstrap 5 + jQuery + DataTables + Chart.js + SweetAlert2 + bs-stepper.
- **Auth:** Laravel Breeze + a custom *exam-portal* secondary login (roll number + 10-char exam password).
- **PDF:** barryvdh/laravel-dompdf.
- **Queue:** database queue.
- **Storage:** local disk for sensitive files (CNIC, license, supporting docs); public disk for profile photos and branding.
- **Theme:** "Electric Ocean" (custom CSS file at [public/css/electric-ocean-theme.css](../public/css/electric-ocean-theme.css)) + dynamic palette overrides driven by super-admin settings.

### Modules / features
1. Auth & accounts — Breeze + soft-deleted [User](../app/Models/User.php), three roles (super_admin, admin, candidate), single-session middleware, **TOTP-based 2FA** (added 2026-04-28).
2. Posts (jobs) — CRUD with quotas (open_merit / female / disabled / minority / district), per-post exam-window toggle.
3. Candidate profile — 8-step wizard with smart completion %, autosave-style step writes.
4. Applications — eligibility check (centralised in EligibilityService since 2026-04-28), admin approve/reject, auto roll number + encrypted exam password on approval, **re-application after rejection** (added 2026-04-28).
5. Roll slip / admit card — self-printable HTML with QR code (added 2026-04-28).
6. Question bank — CRUD + status toggle + **CSV bulk-import** (added 2026-04-28).
7. Online exam engine — locked, fullscreen-enforced, anti-cheat, batched events, heartbeat, CSRF refresh, force-submit termination page.
8. Merit list — post-wise ranking by score then submission time.
9. Reports — profiles, applications, exams, results, security, email logs.
10. Result email — queued job sends PDF answer sheet + branded approval/rejection emails (added 2026-04-28).
11. Audit log — severity tags, presets, JSON/CSV/PDF export, live polling, suspicious-IP analytics.
12. App settings — branding (logo, favicon, theme palette, footer & social links, maintenance message, custom roll-slip notes).
13. Global search with role-aware filtering (⌘K hotkey).
14. Health probe at `/health`.

### Roles & main flows
- **Candidate:** register → wizard → submit profile → admin approves → browse posts → apply → admin approves application (roll# + exam pwd) → log into exam portal → take MCQ exam → result email + PDF.
- **Admin:** create posts/quotas/questions → review profiles → review applications → monitor exam attempts → reset attempts → view merit list → export reports.
- **Super Admin:** all of admin + app settings + audit log + system dashboard.

---

## 2. Gap analysis — Missing or incomplete

### Core recruitment / admissions flow
- ❌ **No fee challan / payment.** No payment models, controllers, gateway integration. (Medium — out-of-scope for this session.)
- ❌ **No offer letter / appointment letter generation.** Merit list exists but no next-step issuance.
- ❌ **No admission confirmation / seat-locking.** No conversion of merit-list winners → "selected" → joining.
- ❌ **No interview / second-stage assessment.** Only MCQ exam exists.
- ❌ **No document verification step distinct from profile approval.**
- ✅ **Eligibility check was shallow** (age + gender only). **Resolved 2026-04-28** — see [app/Services/EligibilityService.php](../app/Services/EligibilityService.php).
- ✅ **No re-application / appeal flow** — `(user_id, post_id)` uniqueness blocked rejected candidates from re-applying. **Resolved 2026-04-28** — re-apply re-purposes the rejected row in-place.

### Exam management
- ❌ **No scheduling.** Exams have a manual `exam_login_enabled` switch but no `start_at` / `end_at` / `exam_centre` / `time_slot` fields.
- ❌ **No seating plan / centre allocation.** Roll slips show no centre, hall, seat number.
- ✅ **No physical attendance capture.** Partially addressed 2026-04-28 — roll slip now carries a signed QR for proctor scan.
- ❌ **No grading other than score = correct count.** No negative marking, no section weighting.
- ❌ **Per-question time limits not enforced** — only total.
- ❌ **No result publishing window.**
- ❌ **No interview score capture / final composite.**

### Communication
- ✅ **Only one outbound email type** (exam result). **Resolved 2026-04-28** — added profile-approved, profile-rejected, application-approved, application-rejected emails (queued, branded, shared layout).
- ❌ **No SMS provider integration.** (Medium — needs provider choice.)
- ✅ **No in-app notifications / bell icon / unread state.** **Resolved 2026-04-29** — Laravel notifications + a `notifications` table + bell-icon dropdown in topbar with 30s poller. Four notification classes parallel the email Mailables (profile/application × approved/rejected). Independent failure domains: SMTP outage doesn't block bell entries and vice versa.
- ❌ **No system-wide announcements / banners** (only the maintenance banner).

### Reporting & analytics
- ✅ **Reports are static, capped at 2,000 rows** with no date-range filtering. **Resolved 2026-04-29** — every report accepts `?from=YYYY-MM-DD&to=YYYY-MM-DD`. New `/admin/reports/{type}/export` streams the **full filtered dataset** as CSV via `chunk(500) + fputcsv` — constant memory, not row-capped.
- ✅ **No conversion-funnel.** **Resolved 2026-04-28** — added 6-stage funnel to admin dashboard (Registered → Profile → Approved → Applied → App-approved → Exam-completed).
- ❌ **No application-trend timeline** (apps/day, apps/post, apps/region).
- ❌ **No geographic / domicile distribution** (despite `domicile_id` and `domicile_district_id` being captured).
- ❌ **No exam analytics**: pass/fail histogram, score distribution, per-question difficulty, item analysis.
- ❌ **No exportable formats beyond DataTables Excel button.**
- ❌ **No data-warehouse-style "completed applications by quarter".**

### Admin tooling
- ✅ **No user/role management UI.** **Resolved 2026-04-29** — full super-admin CRUD at `/super-admin/users` with three self-protection invariants (cannot change own role, cannot deactivate/delete self, cannot leave the system without an active super-admin). Includes 2FA-disable, password-reset-on-behalf-of-user, and toggle-active actions, all audit-logged.
- ✅ **No bulk operations.** **Resolved 2026-04-28** — bulk-approve checkboxes + sticky toolbar on candidates and applications indexes.
- ✅ **No data import.** **Partially resolved 2026-04-28** — CSV bulk-import for questions; posts/candidates remain individual-form-only.
- ❌ **No data export beyond reports.** No GDPR-style "export all my data".
- ✅ **No password reset on behalf of candidates** by admins. **Resolved 2026-04-29** — super-admin "send password reset" action triggers Laravel's standard signed-link flow.
- ❌ **No "merge duplicate candidates"** flow.

### Integrations
- ❌ **No payment gateway** (1LINK / EasyPaisa / JazzCash / HBL).
- ❌ **No identity verification** (NADRA Verisys for CNIC).
- ❌ **No SMS provider.**
- ❌ **No third-party SSO.**
- ❌ **No biometric / proctoring service.**
- ❌ **No file-virus scanning.**

### Compliance & security
- ✅ **Exam password stored in plaintext.** **Resolved 2026-04-28** — `applications.exam_password` is now encrypted-at-rest via Laravel `encrypted` cast (DB exfiltration leaks ciphertext only). Migration: [2026_04_28_150000_encrypt_application_exam_password.php](../database/migrations/2026_04_28_150000_encrypt_application_exam_password.php).
- ✅ **No MFA for admin / super-admin.** **Resolved 2026-04-28** — mandatory TOTP-based 2FA on first login for admin/super_admin, with 8 single-use recovery codes and an Artisan disable command.
- ❌ **No password policy beyond `min(8)+letters+numbers`** — no `mixedCase()`, `symbols()`, no breached-password check.
- ✅ **No login throttling** — verified 2026-04-28: throttle was already in place at both route level (`throttle:5,1`) and per-credential (`LoginRequest::ensureIsNotRateLimited`).
- ❌ **No password rotation or expiry** for admin accounts.
- ❌ **No GDPR / data-rights endpoints.**
- ❌ **No PII redaction in logs** — audit log stores `old_values` / `new_values` JSON which may contain CNIC, phone, full address.
- ✅ **CSP / security headers** not configured. **Resolved 2026-04-29** — `App\Http\Middleware\SecureHeaders` appended to the `web` group emits CSP, HSTS (HTTPS-only), X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy. CSP is intentionally permissive (`'unsafe-inline'` allowed) until inline scripts in Blade templates are migrated out — see audit code-quality items.
- ✅ **No CAPTCHA** on register or login. **Resolved 2026-04-29** — math + honeypot CAPTCHA via `App\Support\SimpleCaptcha`, applied to register and forgot-password (login deliberately skipped — already covered by per-IP throttle + per-(email,IP) Lockout + 2FA on admin/super_admin).

---

## 3. Improvements to existing code

### Code quality
- ⚠️ **Massive controller** — [ProfileWizardController.php](../app/Http/Controllers/Candidate/ProfileWizardController.php) is 841 lines. Each `saveStep*` has its own inline `validateOrFail` rules + storage logic. Recommend splitting per-step into FormRequests + Service handlers. *(Open.)*
- ⚠️ **Massive view** — [exam/show.blade.php](../resources/views/exam/show.blade.php) is 1,363 lines (HTML + inline CSS + ~700 lines of JS). Extract JS to a dedicated `public/js/exam-engine.js`. *(Open.)*
- ⚠️ **Massive view** — [candidate/profile/show.blade.php](../resources/views/candidate/profile/show.blade.php) is 845 lines. Break into Blade components per tab. *(Open.)*
- ⚠️ **Inline `<style>` blocks** in many views (300+ lines in some). Move to component CSS files. *(Partially addressed in design-migration pass — section-card pattern reduces duplication.)*
- ⚠️ **Duplicate `strip_tags(Str::squish(...))` everywhere.** Extract into a `StringSanitizer` service. *(Open.)*
- ⚠️ **Nearly identical stat aggregations** repeated across many controllers. Extract into `App\Services\Stats\PipelineStats` with cached methods. *(Partly addressed — dashboard caches added 2026-04-28; the duplication itself remains.)*
- ✅ **Inconsistent layout extension** — old views still extending `layouts.admin`. **Resolved 2026-04-28** — all views now extend `layouts.app`. The `layouts.admin` shim remains as backward-compat.
- ✅ **Dead/legacy code** — `candidate/profile/{create,edit}.blade.php`. **Resolved 2026-04-28** — deleted.
- ⚠️ **CDN-loaded vendor libs** in [layouts/app.blade.php](../resources/views/layouts/app.blade.php). Should be locally bundled via Vite for offline reliability + CSP. *(Open.)*

### Architecture
- ⚠️ **Business logic in controllers** — application approval, profile approval. *(Partially addressed via EligibilityService for eligibility-checks; approval-services still inline.)*
- ⚠️ **No FormRequest classes for many writes** — e.g., bulk approve actions inline-validate. *(Open.)*
- ⚠️ **Eloquent collections returned to views where pagination is needed** in [Candidate/ApplicationController.php](../app/Http/Controllers/Candidate/ApplicationController.php), [Admin/MeritListController.php](../app/Http/Controllers/Admin/MeritListController.php). Will OOM at scale. *(Open.)*
- ⚠️ **Event/listener system unused.** `AuditLog::record()` called manually 25+ times — should be event-driven. *(Open.)*

### Performance
- ⚠️ **N+1 queries** — [Admin/MeritListController.php](../app/Http/Controllers/Admin/MeritListController.php) doesn't paginate; big posts will choke.
- ⚠️ **`->get()` on big tables** in several admin/candidate listings. Review each.
- ⚠️ **Compound indexes missing** — `(status, created_at)` on `applications` and `exam_attempts` would speed up `latest()->limit()` patterns on dashboards.
- ✅ **No caching of dashboard aggregates.** **Resolved 2026-04-28** — 60-second `Cache::remember()` on admin/super-admin/candidate dashboards (per-user for candidate, role-wide for the others).
- ⚠️ **`applicationsByPost` in admin dashboard** doesn't filter by `status='active'` — closed posts pollute the chart. *(Open.)*
- ✅ **Roll number generation** loaded all roll numbers per post into PHP. **Resolved 2026-04-28** — single SQL `MAX(SUBSTR(...) + 0)`.
- ⚠️ **Document file serving** uses synchronous `response()->file()`. For large files use streamed response. *(Open.)*

### Security
- ✅ **Plaintext exam password** in DB. **Resolved 2026-04-28** (encrypted cast).
- ✅ **No SQL injection** observed.
- ✅ **`{!! $themeCss !!}` raw render** — verified 2026-04-28 as safe (allowlisted preset + regex-validated hex + sprintf-emitted output).
- ⚠️ **XSS risk on `{!! $a['icon'] !!}`** in admin dashboard (hardcoded array, currently safe but pattern is risky).
- ✅ **CSRF** intact via Laravel defaults; exam JS does token-refresh.
- ✅ **File upload validation is solid** at [ProfileWizardController.php](../app/Http/Controllers/Candidate/ProfileWizardController.php) (mimes + mimetypes + dimensions).
- ✅ **`.doc/.docx` accepted as supporting docs** — macro risk. **Resolved 2026-04-28** — PDF + JPG/PNG only.
- ⚠️ **No virus scanning** on uploads. *(Open — Medium priority.)*
- ⚠️ **No file-content sanitization** for JPG/PNG (no EXIF strip / re-encode).
- ✅ **Privileged file paths** are protected (UUID filenames, ownership checks).
- ✅ **Mass assignment**: `$fillable` is well-defined on all models.
- ⚠️ **`Auth::login()` after manual exam-portal credential check** — if a future bug skips that check, anyone with a roll number gets in. Mitigate with a dedicated guard. *(Open.)*

### Validation & error handling
- ⚠️ **Inline validation arrays** rather than FormRequest classes for: candidate apply, exam answer save, exam security log, every wizard step.
- ⚠️ **User-facing errors are sometimes generic** ("Failed to approve. Please try again."). Add a support reference id.

### Testing
- ✅ **Test coverage was essentially 0** for domain logic. **Resolved 2026-04-29** — first wave (E-3) covered `ExamPasswordService` (5), `RollNumberService` (5), `EligibilityService` (11), candidate apply/re-apply flow (8). Second wave (F-2) added admin application-approval/rejection flow (7) and `ExamSubmissionService` (6), plus `SecureHeaders` middleware smoke tests (4) and refreshed `RegistrationTest` + `PasswordResetTest` for the new CAPTCHA. ~50 cases total covering the highest-risk paths.
- ⚠️ **No Dusk / browser tests** for the exam engine. *(Open.)*

### DevOps
- ⚠️ No CI config — no `.github/workflows`, no GitLab CI. *(Open.)*
- ⚠️ No Dockerfile / compose. *(Open.)*
- ⚠️ No `.env.staging` separation. *(Open.)*
- ⚠️ No structured logging. *(Open.)*
- ⚠️ No application monitoring (Sentry, Bugsnag, Telescope). *(Open.)*

### Documentation
- ⚠️ No API docs (server-rendered, so not blocking).
- ⚠️ No CHANGELOG.md despite version `3.0.0`. *(Open.)*
- ⚠️ No CONTRIBUTING.md / coding standards. *(Open.)*

---

## 4. Design & UI/UX (audit notes)

### Visual consistency — resolved
- ✅ **Two button systems** — old `.btn-gov-primary` + new `.btn-primary`. **Resolved 2026-04-28.**
- ✅ **Two form systems** — Bootstrap card chrome on edit pages, section-card on listings. **Resolved 2026-04-28.**
- ✅ **Two icon systems** — Bootstrap-Icons `<i class="bi bi-…">` vs. inline SVGs. **Resolved 2026-04-28** for migrated pages; some scoped CSS still uses inline SVGs.
- ✅ **Two breadcrumb formats.** **Resolved 2026-04-28.**

### Open items
- ⚠️ **Color / spacing tokens not formally enforced** — magic pixel values still appear in many inline `<style>` blocks. Recommend a formal token doc + lint rule. *(Open.)*
- ⚠️ **Three font families** loaded everywhere (Inter, JetBrains Mono, Space Grotesk). Acceptable but worth a cost/benefit review for mobile.
- ⚠️ **Hero banners on every dashboard** consume ~25% of viewport. For high-frequency admins this is wasted real estate. *(Open — design call.)*
- ⚠️ **Dashboard "Quick actions" grid duplicates the sidebar** ([admin/dashboard.blade.php](../resources/views/admin/dashboard.blade.php) lines 222-244). *(Open — design call.)*
- ✅ **Auth pages still on Breeze defaults** — verified 2026-04-29 to be already migrated to Electric Ocean (`auth-form` / `auth-input` / `auth-submit` styling on all five Breeze pages plus the three new 2FA pages). The audit flag was already addressed before the session began. **Bonus cleanup 2026-04-29:** deleted ~21 orphaned Breeze stock files (legacy ProfileController, dashboard.blade.php, layouts/{guest,navigation}.blade.php, profile/* directory, and the components/ x-component definitions that nothing referenced).
- ⚠️ **Email templates** — shared layout added 2026-04-28 ([resources/views/emails/_layout.blade.php](../resources/views/emails/_layout.blade.php)); existing `exam-result.blade.php` not yet migrated to the shared layout.

### Forms / UX
- ⚠️ **Wizard exists for profile** (excellent). Could apply the same pattern to **post creation** (Details → Eligibility → Quotas) and **bulk question creation**. *(Open.)*
- ⚠️ **Required-field marking** — standardised on the migrated pages, still inconsistent on legacy auth/wizard pages.
- ⚠️ **Inline validation only on the wizard** — admin forms rely on full-page reload + Laravel `old()`.
- ⚠️ **No autosave** outside the wizard.

### Tables & lists
- ⚠️ **DataTables used inconsistently** — admin/applications, admin/candidates, admin/questions use it; merit list, candidate my-applications use plain tables.
- ⚠️ **No saved column views / preferences.**
- ✅ **Loading skeletons absent.** **Partially resolved 2026-04-28** — chart canvases now use skeleton overlays; tables don't (server-rendered HTML doesn't need them).

### Accessibility
- ⚠️ **Color contrast** needs a formal WCAG AA pass. Several `var(--text-tertiary)` on `var(--bg-surface)` likely fall short.
- ⚠️ **SVG icons lack ARIA labels** on many pages (some have proper labels — most don't).
- ⚠️ **`aria-current` not set on active nav items.**
- ⚠️ **Focus rings stripped in places.**
- ⚠️ **No skip-to-content link** in the layout.

### Responsive design
- Hero banners, profile show, applications/show have responsive grids.
- ⚠️ Reports tables overflow horizontally on mobile.

### Branding
- ✅ Logo / favicon / palette uploadable via super-admin settings.
- ⚠️ **`emails/exam-result.blade.php`** not yet using the shared layout introduced 2026-04-28. *(Open — small cleanup.)*

### Dark mode
- Implemented across the system. Migrated views are dark-mode aware via theme tokens.

---

## 5. Infographics, charts & data visualization

### Existing
- Admin dashboard has 2 Chart.js charts: applications-by-post (h-bar) and exam-status (donut).
- Application-detail page has a quota donut.
- Audit log has timeline / heatmap / severity charts (mature).
- ✅ **Conversion funnel** added 2026-04-28 (CSS+HTML horizontal bars, no Chart.js dependency).

### Recommended additions (open)
- **Geographic heatmap by domicile** — Pakistan map SVG, color-coded.
- **Gender split donut.**
- **Quota fulfillment per post** stacked bar (filled vs unfilled).
- **Daily peak hours heatmap** for application submissions and exam attempts.
- **Score histogram** (per post + global).
- **Pass/fail distribution donut.**
- **Item analysis** — hardest 10 questions, easiest 10. Useful for question-bank curation.
- **Year-vs-year, batch-vs-batch comparisons** — once historical data exists.

### Recommended charting library
Stick with **Chart.js 4.4** — already adopted, lightweight, accessible enough. For richer interactions add **ApexCharts** for one or two pages (heatmap, funnel, treemap).

### Printable documents
- ✅ **QR code on roll slip** — added 2026-04-28 with HMAC-signed payload.
- ⚠️ **Code128 barcode** for the roll number — not added (proctor app could scan QR for the same data).
- ⚠️ **Answer sheet PDF** — should add page numbers, watermark with roll #.
- ⚠️ **Fee challan / offer letter** — entire feature missing.

---

## 6. Prioritized recommendations (post-implementation status)

### Critical — DONE (Option A, 2026-04-28)
- ✅ C1: Exam password encrypted at rest.
- ✅ C2: Login throttling verified (was already in place).
- ✅ C3: `.doc/.docx` removed from supporting-doc uploads.
- ✅ C4: TOTP-based 2FA mandatory for admin / super-admin.
- ✅ C5: Re-application after rejection.
- ✅ C6: EligibilityService (profile-complete, conviction, dual-nationality checks).
- ✅ C7: `{!! $themeCss !!}` verified safe.
- ✅ C8: Roll number SQL optimised.

### High — DONE (Option B, 2026-04-28)
- ✅ Cache the dashboards.
- ✅ QR code on roll slip.
- ✅ Profile + application approval/rejection emails.
- ✅ Bulk-approve checkboxes.
- ✅ Conversion-funnel chart.
- ✅ Loading skeletons (chart canvases).
- ✅ CSV bulk-import for questions.

### High — DONE (Option E, 2026-04-29)
- ✅ User/role management UI for super-admin.
- ✅ In-app notifications (bell icon + unread state).
- ✅ Re-skin Breeze auth pages — verified already migrated; orphaned Breeze files deleted.
- ✅ Domain test coverage for `EligibilityService`, `RollNumberService`, `ExamPasswordService`, candidate apply/re-apply flow.

### High — DONE (Option F, 2026-04-29)
- ✅ Date-range filter + streamed CSV export for reports.
- ✅ Approval-flow + exam-submission feature tests.
- ✅ CAPTCHA on register / forgot-password (math + honeypot, no third-party deps).
- ✅ CSP / security-headers middleware.

### High — STILL OPEN
- ⚠️ Tighten CSP (drop `'unsafe-inline'`) once inline scripts are migrated out of Blade templates.

### Medium
- ⚠️ Payment gateway integration (1LINK / EasyPaisa / JazzCash / HBL).
- ⚠️ Offer letter / appointment letter.
- ⚠️ Exam scheduling, centres, seating plans.
- ⚠️ SMS provider abstraction.
- ⚠️ GDPR-style data export endpoint.
- ⚠️ Geographic distribution & exam analytics charts.
- ⚠️ Inline `<style>` cleanup → component CSS files.
- ⚠️ Empty-state illustrations.
- ⚠️ Mobile drawer wiring polish.
- ⚠️ ARIA labels audit.

### Low
- ⚠️ Year-vs-year comparison charts.
- ⚠️ Page transitions / view-transitions API.
- ⚠️ Migrate exam-result email to shared layout.
- ⚠️ Microinteraction polish.
- ⚠️ Print-ready infographic PDFs for reports.
- ⚠️ Telescope in dev.

### Design migration — DONE (Option C, 2026-04-28)
- ✅ Deleted dead `candidate/profile/{create,edit}.blade.php` (controllers redirect to wizard).
- ✅ Migrated `admin/posts/{create,edit,show}.blade.php` to Electric Ocean.
- ✅ Migrated `admin/questions/{create,edit,show}.blade.php` to Electric Ocean.
- ✅ Migrated `admin/exam-attempts/show.blade.php` to Electric Ocean.
- ✅ Tidied `super-admin/settings/index.blade.php` (breadcrumb + button consistency).

---

## 7. Quick wins (post-implementation tracking)

| # | Original recommendation | Status |
|---|---|---|
| 1 | Cache the dashboards | ✅ Done |
| 2 | Hash exam password | ✅ Done (encrypted cast) |
| 3 | Throttle login | ✅ Verified already in place |
| 4 | Migrate old Bootstrap pages | ✅ Done |
| 5 | QR code on roll slip | ✅ Done |
| 6 | Approval emails | ✅ Done |
| 7 | Bulk-approve checkboxes | ✅ Done |
| 8 | Conversion-funnel chart | ✅ Done |
| 9 | Loading skeletons | ✅ Done (chart canvases) |
| 10 | CSV bulk-import for questions | ✅ Done |

---

## 8. Implementation log

### Wave 1 — 2026-04-28 (Options A, B, C, D)

#### New files
**Migrations:**
- [database/migrations/2026_04_28_150000_encrypt_application_exam_password.php](../database/migrations/2026_04_28_150000_encrypt_application_exam_password.php)
- [database/migrations/2026_04_28_160000_add_two_factor_columns_to_users.php](../database/migrations/2026_04_28_160000_add_two_factor_columns_to_users.php)

**Services:**
- [app/Services/EligibilityService.php](../app/Services/EligibilityService.php)
- [app/Services/RollSlipQrService.php](../app/Services/RollSlipQrService.php)

**Controllers:**
- [app/Http/Controllers/Auth/TwoFactorController.php](../app/Http/Controllers/Auth/TwoFactorController.php)

**Middleware:**
- [app/Http/Middleware/EnsureTwoFactorPassed.php](../app/Http/Middleware/EnsureTwoFactorPassed.php)

**Mailables:**
- [app/Mail/ProfileApprovedMail.php](../app/Mail/ProfileApprovedMail.php)
- [app/Mail/ProfileRejectedMail.php](../app/Mail/ProfileRejectedMail.php)
- [app/Mail/ApplicationApprovedMail.php](../app/Mail/ApplicationApprovedMail.php)
- [app/Mail/ApplicationRejectedMail.php](../app/Mail/ApplicationRejectedMail.php)

**Views:**
- [resources/views/emails/_layout.blade.php](../resources/views/emails/_layout.blade.php) (shared email chrome)
- [resources/views/emails/profile-approved.blade.php](../resources/views/emails/profile-approved.blade.php)
- [resources/views/emails/profile-rejected.blade.php](../resources/views/emails/profile-rejected.blade.php)
- [resources/views/emails/application-approved.blade.php](../resources/views/emails/application-approved.blade.php)
- [resources/views/emails/application-rejected.blade.php](../resources/views/emails/application-rejected.blade.php)
- [resources/views/auth/two-factor-setup.blade.php](../resources/views/auth/two-factor-setup.blade.php)
- [resources/views/auth/two-factor-recovery.blade.php](../resources/views/auth/two-factor-recovery.blade.php)
- [resources/views/auth/two-factor-challenge.blade.php](../resources/views/auth/two-factor-challenge.blade.php)

#### Deleted files
- `resources/views/candidate/profile/create.blade.php` (legacy / unreachable).
- `resources/views/candidate/profile/edit.blade.php` (legacy / unreachable).

#### Modified files (highlights)
- [composer.json](../composer.json) — added `pragmarx/google2fa-qrcode`, `bacon/bacon-qr-code`.
- [bootstrap/app.php](../bootstrap/app.php) — registered `mfa` middleware alias.
- [routes/auth.php](../routes/auth.php) — six 2FA routes (setup, challenge, recovery).
- [routes/web.php](../routes/web.php) — `mfa` middleware on admin/super-admin route groups; bulk-approve routes; CSV import routes.
- [routes/console.php](../routes/console.php) — `php artisan mfa:disable {email}` recovery command.
- [app/Models/User.php](../app/Models/User.php) — 2FA columns + helpers + encrypted cast.
- [app/Models/Application.php](../app/Models/Application.php) — `exam_password` encrypted cast.
- [app/Services/ExamPasswordService.php](../app/Services/ExamPasswordService.php) — removed broken plaintext-collision check.
- [app/Services/RollNumberService.php](../app/Services/RollNumberService.php) — SQL `MAX(SUBSTR + 0)` aggregate.
- [app/Http/Controllers/Admin/CandidateController.php](../app/Http/Controllers/Admin/CandidateController.php) — bulk-approve + email dispatch.
- [app/Http/Controllers/Admin/ApplicationController.php](../app/Http/Controllers/Admin/ApplicationController.php) — bulk-approve + email dispatch.
- [app/Http/Controllers/Admin/QuestionController.php](../app/Http/Controllers/Admin/QuestionController.php) — CSV import + template.
- [app/Http/Controllers/Candidate/ApplicationController.php](../app/Http/Controllers/Candidate/ApplicationController.php) — re-application + EligibilityService delegation + QR.
- [app/Http/Controllers/Candidate/ProfileWizardController.php](../app/Http/Controllers/Candidate/ProfileWizardController.php) — `.doc/.docx` removed from accepted MIME types.
- [app/Http/Controllers/Auth/AuthenticatedSessionController.php](../app/Http/Controllers/Auth/AuthenticatedSessionController.php) — defers `Auth::login` for admin/super_admin pending 2FA.
- [app/Http/Controllers/Admin/DashboardController.php](../app/Http/Controllers/Admin/DashboardController.php) — cached aggregates + funnel.
- [app/Http/Controllers/Candidate/DashboardController.php](../app/Http/Controllers/Candidate/DashboardController.php) — cached per-user stats.
- [app/Http/Controllers/SuperAdmin/DashboardController.php](../app/Http/Controllers/SuperAdmin/DashboardController.php) — cached aggregates.
- [public/css/electric-ocean-theme.css](../public/css/electric-ocean-theme.css) — appended skeleton-loader primitives.
- [resources/views/admin/dashboard.blade.php](../resources/views/admin/dashboard.blade.php) — funnel section + chart skeletons.
- [resources/views/candidate/my-applications/roll-slip.blade.php](../resources/views/candidate/my-applications/roll-slip.blade.php) — QR styles + block.
- [resources/views/admin/applications/index.blade.php](../resources/views/admin/applications/index.blade.php) — bulk-approve UI.
- [resources/views/admin/candidates/index.blade.php](../resources/views/admin/candidates/index.blade.php) — bulk-approve UI.
- [resources/views/admin/questions/index.blade.php](../resources/views/admin/questions/index.blade.php) — CSV import modal.
- [resources/views/admin/applications/show.blade.php](../resources/views/admin/applications/show.blade.php) — chart skeleton on quota donut.
- [resources/views/admin/posts/{create,edit,show}.blade.php](../resources/views/admin/posts/) — Electric Ocean migration.
- [resources/views/admin/questions/{create,edit,show}.blade.php](../resources/views/admin/questions/) — Electric Ocean migration.
- [resources/views/admin/exam-attempts/show.blade.php](../resources/views/admin/exam-attempts/show.blade.php) — Electric Ocean migration.
- [resources/views/super-admin/settings/index.blade.php](../resources/views/super-admin/settings/index.blade.php) — breadcrumb + button-class consistency pass.
- [resources/views/candidate/available-posts.blade.php](../resources/views/candidate/available-posts.blade.php) — re-apply UX.
- [resources/views/candidate/my-applications/index.blade.php](../resources/views/candidate/my-applications/index.blade.php) — re-apply icon-button.
- [resources/views/candidate/profile/_steps/_documents.blade.php](../resources/views/candidate/profile/_steps/_documents.blade.php) — accept= attribute + help text reflect PDF/image-only.
- [resources/views/layouts/app.blade.php](../resources/views/layouts/app.blade.php) — explanatory comment on `{!! $themeCss !!}` safety.

### Wave 2 — 2026-04-29 (Option E)

#### New files
**Migrations:**
- [database/migrations/2026_04_29_100000_create_notifications_table.php](../database/migrations/2026_04_29_100000_create_notifications_table.php)

**Notifications (database channel — bell-icon entries):**
- [app/Notifications/ProfileApprovedNotification.php](../app/Notifications/ProfileApprovedNotification.php)
- [app/Notifications/ProfileRejectedNotification.php](../app/Notifications/ProfileRejectedNotification.php)
- [app/Notifications/ApplicationApprovedNotification.php](../app/Notifications/ApplicationApprovedNotification.php)
- [app/Notifications/ApplicationRejectedNotification.php](../app/Notifications/ApplicationRejectedNotification.php)

**Controllers:**
- [app/Http/Controllers/NotificationController.php](../app/Http/Controllers/NotificationController.php) — bell dropdown, unread-count poll, mark-read, mark-all.
- [app/Http/Controllers/SuperAdmin/UserController.php](../app/Http/Controllers/SuperAdmin/UserController.php) — full user/role CRUD with three self-protection invariants.

**Views:**
- [resources/views/notifications/index.blade.php](../resources/views/notifications/index.blade.php) — full listing page.
- [resources/views/super-admin/users/index.blade.php](../resources/views/super-admin/users/index.blade.php) — filter + table + per-row actions.
- [resources/views/super-admin/users/create.blade.php](../resources/views/super-admin/users/create.blade.php)
- [resources/views/super-admin/users/edit.blade.php](../resources/views/super-admin/users/edit.blade.php)

**JS:**
- [public/js/notifications.js](../public/js/notifications.js) — bell-icon poller + dropdown renderer + read marker.

**Tests:**
- [tests/Unit/Services/ExamPasswordServiceTest.php](../tests/Unit/Services/ExamPasswordServiceTest.php) — 5 cases (alphabet, length, uppercase-alnum, uniqueness, alphabet-size).
- [tests/Feature/Services/RollNumberServiceTest.php](../tests/Feature/Services/RollNumberServiceTest.php) — 5 cases (first-roll format, sequential increment, per-post isolation, zero-padding, while-loop safety net).
- [tests/Feature/Services/EligibilityServiceTest.php](../tests/Feature/Services/EligibilityServiceTest.php) — 11 cases (happy path + each rule's failure case + multi-failure accumulation).
- [tests/Feature/Candidate/ApplicationApplyFlowTest.php](../tests/Feature/Candidate/ApplicationApplyFlowTest.php) — 8 cases (apply, double-apply blocked, re-apply after rejection, age/gender/conviction/dual-nationality/inactive-post checks).

#### Modified files
- [routes/web.php](../routes/web.php) — added `/notifications/*` routes (auth) and `/super-admin/users/*` routes (super_admin + mfa).
- [resources/views/layouts/partials/topbar.blade.php](../resources/views/layouts/partials/topbar.blade.php) — bell icon + dropdown markup + scoped CSS.
- [resources/views/layouts/partials/sidebar.blade.php](../resources/views/layouts/partials/sidebar.blade.php) — "Users" link in the Super-admin → System section.
- [resources/views/layouts/app.blade.php](../resources/views/layouts/app.blade.php) — `js/notifications.js` loaded for authenticated users.
- [app/Http/Controllers/Admin/CandidateController.php](../app/Http/Controllers/Admin/CandidateController.php) — `notify(...)` calls alongside the existing `Mail::send(...)` in `approve()`, `reject()`, `bulkApprove()`.
- [app/Http/Controllers/Admin/ApplicationController.php](../app/Http/Controllers/Admin/ApplicationController.php) — same parallel notification dispatch in `approve()`, `reject()`, `bulkApprove()`.

#### Deleted files (Breeze stock cluster — orphaned, no routes referenced them)
- `app/Http/Controllers/ProfileController.php`
- `resources/views/dashboard.blade.php`
- `resources/views/layouts/guest.blade.php`
- `resources/views/layouts/navigation.blade.php`
- `resources/views/profile/edit.blade.php` and `resources/views/profile/partials/*`
- `resources/views/components/` directory (13 x-component definitions)

### Wave 3 — 2026-04-29 (Option F — hardening)

#### New files
**Middleware:**
- [app/Http/Middleware/SecureHeaders.php](../app/Http/Middleware/SecureHeaders.php) — CSP, HSTS (HTTPS-only), X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy; strips X-Powered-By.

**Support:**
- [app/Support/SimpleCaptcha.php](../app/Support/SimpleCaptcha.php) — zero-deps math + honeypot service.

**Views:**
- [resources/views/partials/_report-filter.blade.php](../resources/views/partials/_report-filter.blade.php) — date-range filter + CSV-export button bar; included in all six report views.

**Tests:**
- [tests/Feature/Admin/ApplicationApprovalFlowTest.php](../tests/Feature/Admin/ApplicationApprovalFlowTest.php) — 7 cases.
- [tests/Feature/Services/ExamSubmissionServiceTest.php](../tests/Feature/Services/ExamSubmissionServiceTest.php) — 6 cases.
- [tests/Feature/Middleware/SecureHeadersTest.php](../tests/Feature/Middleware/SecureHeadersTest.php) — 4 cases.

#### Modified files
- [app/Http/Controllers/Admin/ReportController.php](../app/Http/Controllers/Admin/ReportController.php) — accepts `?from=YYYY-MM-DD&to=YYYY-MM-DD` on every method; adds `export(string $type)` that streams a CSV via `chunk(500) + fputcsv`.
- [routes/web.php](../routes/web.php) — added `/admin/reports/{type}/export` (throttled 6/min).
- [resources/views/admin/reports/{profiles,applications,exams,results,security,email}.blade.php](../resources/views/admin/reports/) — included `_report-filter` partial.
- [bootstrap/app.php](../bootstrap/app.php) — appended `SecureHeaders::class` to the `web` middleware group.
- [app/Http/Requests/Auth/CandidateRegisterRequest.php](../app/Http/Requests/Auth/CandidateRegisterRequest.php) — captcha + honeypot rules and post-validation hook.
- [app/Http/Controllers/Auth/RegisteredUserController.php](../app/Http/Controllers/Auth/RegisteredUserController.php) — generates a fresh challenge on every render.
- [app/Http/Controllers/Auth/PasswordResetLinkController.php](../app/Http/Controllers/Auth/PasswordResetLinkController.php) — captcha-protected forgot-password POST.
- [resources/views/auth/register.blade.php](../resources/views/auth/register.blade.php) — math captcha + honeypot fields.
- [resources/views/auth/forgot-password.blade.php](../resources/views/auth/forgot-password.blade.php) — same.
- [tests/Feature/Auth/RegistrationTest.php](../tests/Feature/Auth/RegistrationTest.php) — refreshed for captcha + correct redirect target.
- [tests/Feature/Auth/PasswordResetTest.php](../tests/Feature/Auth/PasswordResetTest.php) — primes the captcha session before POSTing.

---

### Required deploy steps
1. `composer install` — pulls `pragmarx/google2fa-qrcode` + `bacon/bacon-qr-code`.
2. `php artisan migrate` — encrypts `applications.exam_password`, adds `users.two_factor_*` columns, creates `notifications` table.
3. **Comms:** notify admins/super-admins they will be required to enrol 2FA on their next login.
4. (Optional) `php artisan view:clear` — flush Blade cache so migrated layouts and topbar bell pick up cleanly.
5. (Recommended) `php artisan test` — runs the new domain test suite to verify the build.

### Post-deploy operational notes
- **Lost authenticator phone:** server admin can either run `php artisan mfa:disable user@example.com` or use the super-admin UI's "Disable 2FA" action on the user row. Both paths are audit-logged.
- **Email driver in dev:** [.env.example](../.env.example) sets `MAIL_MAILER=log` so approval emails land in `storage/logs/laravel.log`.
- **Notifications driver:** the `database` channel writes to the `notifications` table directly. The bell-icon dropdown polls every 30s when the topbar is rendered. No queue worker required for the notification side; the email side still needs a queue worker (or `QUEUE_CONNECTION=sync` in dev).
- **Cache TTL:** dashboard aggregates are 60-second-stale by design — no explicit invalidation on mutations. Detail pages always reflect changes immediately.
- **CSV import format:** download the template via the "CSV template" button on the questions index. Per-row failures are surfaced in the post-upload flash banner, not the overall response.
- **Last super-admin invariant:** the user-management UI prevents deactivating, demoting, or deleting the only remaining active super-admin. To rotate ownership, promote a second super-admin first, then change the original.
- **Report export:** `/admin/reports/{type}/export?from=…&to=…` streams the **full** filtered set as CSV. The on-screen table is still capped at 2,000 rows for memory safety; the export is not. Throttled at 6/min per admin.
- **CAPTCHA replay protection:** the math answer is pulled out of the session on `verify()` regardless of pass/fail, so a captured form post can't be replayed against the same session. Tests prime the captcha by GETting the page first and pulling the session value before POSTing.
- **CSP exceptions:** `'unsafe-inline'` is currently allowed for both script and style because of inline `<script>` and `<style>` blocks across many Blade templates. Tighten by migrating those out (audit code-quality items) and adopting a nonce strategy. The CDN allow-list (jsdelivr, cdnjs, datatables, fonts.googleapis.com) is the minimum surface required by the current vendor-loading pattern.
