A production-grade REST API where citizens file complaints and administrators manage, assign, and resolve them through a structured workflow — built with Laravel.
Request → routes/api.php
│
├── Middleware (auth:sanctum, throttle)
├── FormRequest (validates input → 422 if invalid)
├── Policy (authorizes action → 403 if denied)
├── Controller (thin — delegates to Service)
├── Service (business logic, state machine)
├── Model / Eloquent (DB queries, relationships)
│ └── Observer (auto-logs to activities table)
├── Event → Queued Listener → Notification (email)
└── API Resource (shapes JSON response)
- Backend: PHP 8.3, Laravel 13
- Database: MySQL
- Auth: Laravel Sanctum (token-based)
- Queue: Database driver (swappable to Redis)
- API Docs: Scramble (auto-generated OpenAPI at
/docs/api) - Email: SMTP (Mailpit for local dev)
- Tests: PHPUnit — 34 feature tests, 80 assertions
- Token authentication — register, login, logout, token revocation via Sanctum
- Role-based access — citizen vs admin with PHP 8.1 backed enums
- Row-level authorization — Policies ensure citizens only access own data
- Complaint lifecycle — state machine:
open → in_progress → resolved | rejected, illegal transitions rejected with 422 - Admin assignment — assign complaints to any admin, validates target is actually an admin
- Comments — threaded conversation between citizen and admin on each complaint
- File attachments — upload (jpeg/png/gif/pdf/doc, max 5MB), download with auth check, delete
- Filtering & search —
?status=open,?search=keyword,?sort=-created_at,?assigned_to=me - Pagination — all list endpoints return
data,links,meta - Event-driven notifications — status change dispatches event → queued listener sends email to citizen
- Audit trail — model Observer logs every create/update/delete with old/new value diffs
- Soft deletes — complaints are never permanently removed
- Rate limiting — login endpoint throttled to prevent brute force
- Eager loading —
->with()prevents N+1 queries,whenLoaded()in Resources
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
| POST | /api/auth/register |
Public | Register citizen |
| POST | /api/auth/login |
Public | Login, get token |
| POST | /api/auth/logout |
Token | Revoke token |
| GET | /api/auth/me |
Token | View own profile |
| GET | /api/admins |
Token | List admin users |
| GET | /api/complaints |
Token | List (citizen=own, admin=all) |
| POST | /api/complaints |
Token | File complaint |
| GET | /api/complaints/{id} |
Owner/Admin | View single |
| PATCH | /api/complaints/{id} |
Admin | Update status |
| DELETE | /api/complaints/{id} |
Owner(open)/Admin | Soft delete |
| POST | /api/complaints/{id}/assign |
Admin | Assign to admin |
| GET | /api/complaints/{id}/comments |
Owner/Admin | List comments |
| POST | /api/complaints/{id}/comments |
Owner/Admin | Add comment |
| POST | /api/complaints/{id}/attachments |
Owner/Admin | Upload file |
| GET | /api/complaints/{id}/activity |
Owner/Admin | Audit trail |
| GET | /api/attachments/{id} |
Owner/Admin | Download file |
| DELETE | /api/attachments/{id} |
Admin | Delete file |
# Clone
git clone https://github.com/iamrahulroyy/grievance-system.git
cd grievance-system
# Install dependencies
composer install
# Environment
cp .env.example .env
php artisan key:generate
# Configure database in .env
# DB_CONNECTION=mysql
# DB_DATABASE=grievance_system
# DB_USERNAME=root
# DB_PASSWORD=
# Run migrations + seed test data
php artisan migrate --seed
# Start the server
php artisan serveOpen http://localhost:8000/docs/api for interactive API documentation.
# Install Mailpit
brew install mailpit
# Start Mailpit
mailpit
# Update .env
# MAIL_MAILER=smtp
# MAIL_HOST=127.0.0.1
# MAIL_PORT=1025
# Run the queue worker in a separate terminal
php artisan queue:workOpen http://localhost:8025 to see emails when complaint status changes.
| Role | Password | |
|---|---|---|
| Admin | [email protected] | password |
| Citizen | [email protected] | password |
php artisan testTests: 34 passed (80 assertions)
Duration: <1s
Tests cover: auth flow, registration validation, role-based access, row-level authorization, state machine transitions, event dispatch, filtering, pagination, admin assignment validation, and activity logging.
app/
├── Enums/ ComplaintStatus, UserRole
├── Events/ ComplaintStatusChanged
├── Http/
│ ├── Controllers/ AuthController, ComplaintController, CommentController, AttachmentController
│ ├── Requests/ RegisterRequest, LoginRequest, StoreComplaintRequest, UpdateComplaintStatusRequest, StoreCommentRequest
│ └── Resources/ UserResource, ComplaintResource, CommentResource, AttachmentResource, ActivityResource
├── Listeners/ SendStatusChangedNotification (queued)
├── Models/ User, Complaint, Comment, Attachment, Activity
├── Notifications/ ComplaintStatusChangedNotification (email)
├── Observers/ ComplaintObserver (audit log)
├── Policies/ ComplaintPolicy
├── Providers/ AppServiceProvider (rate limits + Scramble auth)
└── Services/ ComplaintService (state machine + business logic)
database/
├── migrations/ 11 migration files
├── factories/ UserFactory, ComplaintFactory, CommentFactory
└── seeders/ DatabaseSeeder (1 admin + 4 citizens + 16 complaints)
tests/Feature/ AuthTest, ComplaintTest, CommentTest (34 tests)
- Service layer — business logic (state machine, validation) lives in
ComplaintService, not controllers. Controllers are thin glue. - PHP 8.1 enums —
ComplaintStatusandUserRoleare backed enums withcanTransitionTo()method. Type-safe, no magic strings. - Policies over inline checks — authorization is declarative (
Gate::authorize('view', $complaint)), notif ($user->id !== $complaint->user_id)scattered in controllers. - FormRequests over controller validation — validation runs before the controller method. Reusable, testable, keeps controllers clean.
- API Resources over raw model output — explicit JSON contract.
whenLoaded()prevents accidental N+1 queries. - Observer for audit trail — model events fire automatically on every save. No manual logging calls needed anywhere.
- Queued notifications — status change emails don't block the API response. Listener implements
ShouldQueue, processed by the queue worker. - Soft deletes — complaints are never permanently lost.
SoftDeletestrait addsdeleted_atcolumn, queries auto-exclude deleted rows. - Local storage (swappable) — attachments use Laravel's
Storagefacade withlocaldisk. Production swap tos3is a one-word config change. - Dual output layers — same Models, Services, and Policies power both the JSON API (
/api/*) and the Blade UI (/dashboard,/complaints/*). MVC in practice — only the View layer changes.
- Department routing — auto-route complaints to Water, Electricity, Roads departments based on category. Each department sees only their complaints.
- SLA tracking & escalation — deadline per complaint (e.g., 72 hours). Auto-escalate to senior admin with alert email if overdue. Powered by Laravel scheduled commands.
- Public complaint tracking — citizen enters complaint ID + phone number on a public page to check status without logging in. Like courier tracking.
- Dashboard analytics — charts for complaints per week, average resolution time, top categories, busiest wards.
- Bulk status update — admin selects multiple complaints and updates status in one action.
- API versioning —
/api/v1/prefix for backward compatibility. - UUID primary keys — replace sequential IDs to prevent information leakage.
- Full-text search — replace
LIKEqueries with Meilisearch for instant, typo-tolerant search. - S3 file storage — swap
localdisk tos3for scalable cloud storage. - Redis queue driver — replace database queue with Redis for faster job processing.
- CI/CD pipeline — GitHub Actions running
php artisan teston every push, block merge on failure.
- Multi-language support (i18n) — Hindi, Bengali, English. Laravel's built-in localization for complaint forms, emails, and UI.
- Webhook notifications — POST to configured URLs on status change for third-party integrations.
- Granular admin permissions — viewer (read-only), handler (assign + status), super-admin (everything). Permission matrix instead of single admin role.
- Complaint merging — mark duplicates, merge into one, notify all affected citizens.
- Audit log export — download complaint history as CSV/PDF for RTI (Right to Information) compliance.
- Certificate generation — auto-generate resolution certificates (PDF) when a complaint is resolved, downloadable by the citizen as proof of resolution.
When a complaint is marked as resolved, the system will auto-generate a Resolution Certificate — a downloadable PDF that serves as official proof.
What the certificate includes:
- Complaint ID, title, and description
- Citizen name and filing date
- Admin who resolved it and resolution date
- Status timeline (open → in_progress → resolved)
- QR code linking to the complaint's public tracking page for verification
- Official portal branding
Technical approach:
- Generate PDF using
barryvdh/laravel-dompdf(HTML-to-PDF) - Certificate template as a Blade view (
resources/views/certificates/resolution.blade.php) - Triggered automatically via the existing
ComplaintStatusChangedevent when status becomesresolved - Stored as an attachment on the complaint, downloadable via
GET /api/complaints/{id}/certificate - QR code generated using
simplesoftwareio/simple-qrcode
Why this matters for a government portal: Citizens need documented proof that their complaint was addressed — for follow-ups, escalations, or RTI requests. A digital certificate with a verifiable QR code is the modern equivalent of an office stamp.