A production-grade, real-time transportation management system built with ASP.NET Core 8 and Clean Architecture.
Wasla digitizes Egypt's informal microbus transit system — replacing paper-based station management with real-time GPS tracking, encrypted QR operations, automated trip lifecycle management, and WhatsApp-powered OTP authentication.
Explore the live production environment of the Wasla platform:
- Swagger API Documentation - Explore and test the ASP.NET Core REST APIs.
- Admin Dashboard - The web-based station management portal built with Angular.
- Mobile App Download - Download the Flutter-based passenger and driver application.
- Live Links
- The Problem We Solve
- The Solution & Features
- Project Ecosystem
- Architecture Overview
- Tech Stack
- Clean Architecture
- Domain Model
- Authentication & Authorization
- API Reference
- SignalR Real-Time Hubs
- Background Jobs
- Key Engineering Decisions
- WhatsApp Integration
- Docker Deployment
- Getting Started
- Environment Variables
- Project Structure
Every day in Egypt, over 14 million people rely on informal microbuses for transportation. Despite being the backbone of public transit, the system operates almost entirely manually using paper notebooks and verbal communication.
This creates massive inefficiencies across the board:
- For Passengers: No visibility into bus availability. Passengers don't know if they will wait 5 minutes or an hour, cannot track incoming buses, and frequently fall victim to overcharging because official fares aren't digitized.
- For Drivers: Wasted hours sitting in unmanaged queues without knowing their exact turn. Disputes frequently break out over line-cutting and manual queue mismanagement.
- For Station Staff: Managing hundreds of vehicles daily with paper notebooks leads to slow dispatch times, human error, and chaos at the station gates.
- For Management: Zero live visibility. Managers cannot see how many trips are completed, whether the station is running at capacity, or track passenger complaints.
Wasla was built to solve this by introducing real-time tracking, digital queues, encrypted QR authentication, and automated workflows to an industry that has operated offline for decades.
Wasla digitizes the entire microbus transportation workflow, offering dedicated applications and dashboards tailored to each role:
- Passengers: View available microbuses at any station, track incoming buses in real time on a live map, and see exact ETAs and official fares. Passengers can find the nearest station, save favorite routes, and file reports against driver misconduct.
- Drivers: Gain full visibility into their exact queue position. Drivers know exactly who is ahead of them and their estimated wait time. They check in and check out using encrypted QR codes—eliminating paper and verbal arguments.
- Station Staff: Manage the queue digitally. When a driver scans their QR code at the gate, the system automatically validates the token, updates the queue, and creates a trip. No manual data entry needed.
- Station Managers: Access a comprehensive web dashboard with live analytics, real-time monitoring, trip reports, driver management, and a complete reporting system to resolve passenger complaints.
- System Admins: Have full control over the platform's infrastructure. Admins can create and manage stations (update names, map locations, or perform soft deletes), register station managers, reassign managers between stations, monitor background scheduled tasks via the Hangfire dashboard, and manage the WhatsApp messaging infrastructure (authenticating and monitoring the Node.js sender engine via the dedicated WhatsApp dashboard).
This repository is the central backend API for the Wasla platform. The complete system is a distributed ecosystem consisting of multiple specialized applications and microservices.
| Repository | Description | Link |
|---|---|---|
| Backend (This Repo) | Main ASP.NET Core backend | GraduationProject |
| Dashboard | Angular management dashboard | smart-bus-web |
| Mobile | Flutter passenger and driver application | smart-microbus |
| WhatsApp Sender | ASP.NET Core messaging gateway | whatsapp-sender |
| WhatsApp Service | Node.js headless WhatsApp engine | whatsapp-service |
| WhatsApp Dashboard | Angular WhatsApp dashboard | whatsapp-dashboard |
graph TB
subgraph Clients
FL["Flutter Mobile App (Passengers · Drivers · Staff)"]
ANG["Angular Dashboard (Admins · Managers)"]
WAC["WhatsApp Client (QR Scanner Dashboard)"]
end
subgraph "ASP.NET Core 8 Backend"
API["API Layer (Controllers · Middleware · Hubs · Filters)"]
CORE["Core Layer (Services · Entities · DTOs · Contracts)"]
INFRA["Infrastructure Layer (Repositories · DbContext · UoW)"]
end
subgraph Infrastructure
SQL[("SQL Server 2022 (+ NetTopologySuite)")]
REDIS[("Redis 7 (Cache)")]
HF["Hangfire (Background Jobs)"]
end
subgraph "External Services"
OSRM["OSRM (Routing Engine)"]
WAS["WhatsApp Sender API (Gateway)"]
WASVC["WhatsApp Service (Node.js + Puppeteer)"]
end
FL -->|REST + SignalR| API
ANG -->|REST + SignalR| API
WAC -->|SignalR| WAS
API --> CORE
CORE --> INFRA
INFRA --> SQL
INFRA --> REDIS
CORE -->|HTTP| OSRM
CORE -->|HTTP + x-node-token| WAS
WAS -->|HTTP| WASVC
HF --> CORE
style API fill:#4FC3F7,color:#000
style CORE fill:#81C784,color:#000
style INFRA fill:#FFB74D,color:#000
graph LR
subgraph "4 SignalR Hubs"
H1["DriverQueueHub (Queue position updates)"]
H2["DriverDashboardHub (Status changes)"]
H3["LocationTrackingHub (GPS coordinates)"]
H4["RouteTrackingHub (Live route counts + ETA)"]
end
subgraph "4 Realtime Services"
S1["SignalRQueueNotificationService"]
S2["SignalRDashboardNotificationService"]
S3["LocationBroadcastService"]
S4["SignalRRouteTrackingNotificationService"]
end
S1 --> H1
S2 --> H2
S3 --> H3
S4 --> H4
| Category | Technologies |
|---|---|
| Runtime | ASP.NET Core 8, C# 12, .NET 8 |
| Database | SQL Server 2022, Entity Framework Core 8, NetTopologySuite (Spatial) |
| Cache | Redis 7 (distributed), IMemoryCache (in-process) |
| Real-Time | SignalR WebSockets (4 hubs) |
| Auth | ASP.NET Identity, JWT (HMAC-SHA256), Refresh Token Rotation |
| Background Jobs | Hangfire with SQL Server Storage + Hangfire.Console |
| Routing Engine | OSRM (Open Source Routing Machine) |
| Security | AES-GCM Encryption, bcrypt OTP Hashing, Rate Limiting |
| Containerization | Docker, Docker Compose, Multi-Stage Builds |
| API Design | RESTful, API Versioning (v1/v2), Swagger/OpenAPI |
| Reporting | ClosedXML (Excel Generation) |
| Image Processing | SixLabors.ImageSharp |
| Mapping | AutoMapper |
| Localization | IStringLocalizer + .resx (Arabic/English) |
| Logging | Serilog |
graph TB
subgraph "SmartMicrobus.API"
direction TB
CTRL["Controllers (11 controllers)"]
HUBS["SignalR Hubs (4 hubs)"]
MW["Middleware (Exception · Hangfire JWT)"]
RT["Realtime Services (4 notification services)"]
FILT["Filters (CustomAuthorizeFilter)"]
PROG["Program.cs (DI · Pipeline · Config)"]
end
subgraph "SmartMicrobus.Core"
direction TB
SVC["Services (10 service directories)"]
SC["ServiceContracts (11 interface directories)"]
ENT["Domain Entities (16 entities)"]
DTO_["DTOs (86 data transfer objects)"]
ENUM["Enums (15 enumerations)"]
IDENT["Identity Entities (ApplicationUser · ApplicationRole)"]
OPTS["Options (Configuration POCOs)"]
HELP["Helpers (ApiResponse · Pagination · GeoValidator)"]
end
subgraph "SmartMicrobus.Infrastructure"
direction TB
REPO["Repositories (17 implementations)"]
UOW["Unit of Work"]
DBCTX["ApplicationDbContext (EF Core configuration)"]
MIG["Migrations"]
end
CTRL --> SC
HUBS --> SC
RT --> HUBS
SVC -.implements.-> SC
SVC --> UOW
REPO -.implements.-> SC
UOW --> DBCTX
DBCTX --> ENT
style CTRL fill:#4FC3F7,color:#000
style SVC fill:#81C784,color:#000
style REPO fill:#FFB74D,color:#000
| Layer | Depends On | Never Depends On |
|---|---|---|
| API | Core | Infrastructure (directly) |
| Core | Nothing | API, Infrastructure |
| Infrastructure | Core | API |
erDiagram
ApplicationUser ||--o| Driver : "is a"
ApplicationUser ||--o| Passenger : "is a"
ApplicationUser ||--o| Manager : "is a"
ApplicationUser ||--o| Staff : "is a"
ApplicationUser ||--o| Photo : "has"
Station ||--o{ Route : "has routes"
Station ||--o{ Queue : "has queues"
Station ||--o{ Manager : "managed by"
Station ||--o{ Staff : "staffed by"
Route ||--o{ Queue : "has queue"
Route ||--o{ Microbus : "serves"
Route ||--o{ Trip : "traveled on"
Route ||--o{ FavoriteRoute : "favorited"
Driver ||--o| Microbus : "drives"
Driver ||--o{ QueueItem : "queues in"
Driver ||--o{ Trip : "makes trips"
Driver ||--o{ DriverReport : "reported"
Queue ||--o{ QueueItem : "contains"
Passenger ||--o{ DriverReport : "files"
Passenger ||--o{ FavoriteRoute : "saves"
DriverReport ||--o{ DriverReportReason : "has reasons"
ReportReason ||--o{ DriverReportReason : "referenced by"
ApplicationUser {
Guid Id PK
string DisplayName
string PhoneNumber
string RefreshToken
bool IsDeleted
}
Station {
Guid Id PK
string Name
Point Location
string Address
}
Route {
Guid Id PK
string Name
decimal Fare
double Distance
double Duration
Guid FromStationId FK
Guid ToStationId FK
}
Driver {
Guid Id PK
string LicenseNumber
Guid UserId FK
Guid StationId FK
}
Microbus {
Guid Id PK
string PlateNumber
string QrCode
string Model
string Color
int PassengerCount
Guid RouteId FK
Guid DriverId FK
}
Trip {
Guid Id PK
TripStatus Status
DateTimeOffset StartTime
DateTimeOffset EndTime
Guid DriverId FK
Guid RouteId FK
}
QueueItem {
Guid Id PK
int Position
QueueStatus Status
DateTimeOffset JoinedAt
Guid QueueId FK
Guid DriverId FK
}
DriverReport {
Guid Id PK
string Description
ReportStatus Status
DateTimeOffset CreatedAt
Guid PassengerId FK
Guid DriverId FK
}
| Enum | Values | Used In |
|---|---|---|
UserRole |
Admin, Manager, Staff, Driver, Passenger, Owner | JWT claims, [Authorize] |
DriverDashboardStatus |
Available, InQueue, OnTrip | Driver mobile app |
QueueStatus |
Waiting, YourTurn, Skipped, InTrip | Queue management |
TripStatus |
Started, Completed, Cancelled | Trip lifecycle |
ReportStatus |
Pending, Reviewed | Complaint resolution |
TransportMode |
Driving, Walking | OSRM route queries |
sequenceDiagram
participant User
participant API
participant AuthService
participant Identity
participant WhatsApp
participant JWT
User->>API: POST /register-driver
API->>AuthService: RegisterDriverAsync()
AuthService->>Identity: CreateAsync(user)
AuthService->>AuthService: Generate 6-digit OTP
AuthService->>Identity: Hash OTP via PasswordHasher
AuthService->>Identity: Store as UserToken
AuthService->>WhatsApp: Send OTP via HTTP
API-->>User: 200 OK (awaiting confirmation)
User->>API: POST /confirm-account {phone, otp}
API->>AuthService: ConfirmAccountAsync()
AuthService->>Identity: Verify hashed OTP
AuthService->>JWT: Generate JWT + RefreshToken
API-->>User: 200 OK {accessToken, refreshToken}
User->>API: POST /generate-new-jwt-token
API->>AuthService: RefreshTokenAsync()
AuthService->>Identity: Validate + Rotate RefreshToken
AuthService->>JWT: Generate new JWT
API-->>User: 200 OK {newAccessToken, newRefreshToken}
| Claim | Source | Purpose |
|---|---|---|
sub (NameIdentifier) |
User.Id | User identification |
role |
ASP.NET Identity Roles | RBAC authorization |
StationId |
Manager/Staff entity | Station-scoped operations |
jti |
New GUID per token | Token uniqueness |
| Endpoint Group | Admin | Manager | Staff | Driver | Passenger | Anonymous |
|---|---|---|---|---|---|---|
| Account (register, login) | ✅ | |||||
| Admin (users, managers) | ✅ | |||||
| Manager (CRUD, dashboard) | ✅ | |||||
| Staff (check-in/out) | ✅ | |||||
| Driver (location, history) | ✅ | |||||
| Reports (create) | ✅ | |||||
| Reports (admin view) | ✅ | |||||
| Routes (read) | ✅ | |||||
| Stations (read) | ✅ |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /register-driver |
— | Register new driver |
| POST | /register-passanger |
— | Register new passenger |
| POST | /login |
— | Login with phone + password |
| POST | /confirm-account |
— | Confirm with OTP |
| POST | /resend-confirmation |
— | Resend OTP |
| POST | /forgot-password |
— | Initiate password reset |
| POST | /verify-otp |
— | Verify reset OTP |
| POST | /reset-password |
— | Set new password |
| POST | /generate-new-jwt-token |
— | Refresh JWT |
| POST | /logout |
Bearer | Invalidate refresh token |
| GET | /me |
Bearer | Get current user profile |
| PATCH | /upload-photo |
Bearer | Upload profile photo |
| DELETE | /delete-photo |
Bearer | Remove profile photo |
| DELETE | /delete |
Bearer | Soft delete account |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /{driverId} |
Manager | Get driver by ID |
| GET | /license/{licenseNumber} |
Manager | Get driver by license |
| GET | /get-current-postion |
Driver | Get dashboard status |
| GET | /get-driver-queue |
Driver | Get queue position |
| POST | /end-trip |
Driver | End active trip |
| GET | /history |
Driver | Paginated trip history |
| GET | /get-by-plate-number |
— | Lookup by plate |
| POST | /location |
Driver | Update GPS location |
| GET | /location/{driverId} |
Auth | Get driver location + ETA |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /check-in |
Staff | QR scan → join queue |
| POST | /check-out |
Staff | QR scan → create trip |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /add-driver |
Manager | Register driver to station |
| POST | /add-microbus |
Manager | Add microbus |
| PUT | /update-microbus/{id} |
Manager | Update microbus |
| DELETE | /delete-microbus/{id} |
Manager | Remove microbus |
| POST | /assign-driver-microbus |
Manager | Link driver ↔ microbus |
| GET | /dashboard/overview |
Manager | Station dashboard stats |
| GET | /station-drivers |
Manager | Paginated drivers |
| POST | /station-staff |
Manager | Add staff member |
| PUT | /station-staff/{id} |
Manager | Update staff |
| DELETE | /station-staff/{id} |
Manager | Remove staff |
| GET | /station-staff |
Manager | Paginated staff |
| GET | /export-station-data |
Manager | Excel: trips by date range |
| GET | /export-station-drivers |
Manager | Excel: all station drivers |
| GET | /export-station-routes |
Manager | Excel: all station routes |
| GET | /export-station-microbuses |
Manager | Excel: all microbuses |
| GET | /export-reports |
Manager | Excel: reports |
| POST | /check-in |
Manager | QR scan (manager role) |
| GET | /{driverId}/driver-history |
Manager | View any driver's history |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | / |
— | All routes (location data) |
| GET | /all |
Manager | Paginated routes for station |
| GET | /destinations |
— | Destinations from a station |
| GET | /{routeId}/summary |
— | Route summary (fare, distance) |
| GET | /{routeId}/station-microbuses |
— | Available microbuses |
| GET | /{routeId}/on-the-way |
— | Incoming microbuses |
| GET | /route |
— | OSRM route between coordinates |
| POST | /add-route |
Manager | Create route |
| PATCH | /update-route |
Manager | Update route |
| DELETE | /delete-route |
Manager | Delete route |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | / |
— | All stations |
| GET | /{id} |
— | Station by ID |
| GET | /nearest |
— | Nearest station (lat, lng, mode) |
| GET | /{id}/details-with-route |
— | Station + route from user location |
| GET | /route-between |
— | Route between two stations |
| POST | / |
Admin | Add station |
| PUT | /{id} |
Admin | Update station |
| DELETE | /{id} |
Admin | Delete station |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | / |
Passenger | File complaint |
| GET | / |
Passenger | My reports (paginated) |
| GET | /{id} |
Passenger | Report details |
| PUT | /{id} |
Passenger | Update report |
| DELETE | /{id} |
Passenger | Delete report |
| GET | /reasons |
— | Available report reasons |
| GET | /admin/all |
Manager | All reports for station |
| GET | /admin/{id} |
Manager | Report detail (manager view) |
| PATCH | /admin/{id}/status |
Manager | Update report status |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /add-manager |
Admin | Create station manager |
| GET | /users |
Admin | All users (paginated) |
| GET | /users/{id} |
Admin | User by ID |
| POST | /users/{id}/lock |
Admin | Lock account |
| POST | /users/{id}/unlock |
Admin | Unlock account |
| DELETE | /managers/{id} |
Admin | Delete manager |
| PUT | /managers/{id}/station |
Admin | Reassign manager's station |
| Hub | Path | Auth | Purpose |
|---|---|---|---|
DriverQueueHub |
/hubs/driver-queue |
Driver (JWT) | Queue position updates |
DriverDashboardHub |
/hubs/driver-dashboard |
Driver (JWT) | Status change notifications |
LocationTrackingHub |
/hubs/location-tracking |
— | Live GPS tracking |
RouteTrackingHub |
/hubs/route-tracking |
— | Route-level live counts + ETA |
sequenceDiagram
participant Driver
participant API
participant LocationService
participant OSRM
participant RouteTrackingHub
participant Passengers
Driver->>API: POST /driver/location {lat, lng}
API->>LocationService: UpdateDriverLocationAsync()
LocationService->>LocationService: Haversine distance check (>30m?)
alt Distance > 30 meters
LocationService->>OSRM: GetRouteAsync(driver to destination)
OSRM-->>LocationService: {distance, duration, polyline}
LocationService->>RouteTrackingHub: BroadcastRouteUpdate()
RouteTrackingHub-->>Passengers: "ReceiveRouteUpdate" {counts, ETA}
LocationService->>LocationService: Cache in Redis
else Distance ≤ 30 meters
LocationService->>LocationService: Skip (driver stationary)
end
| Job | Schedule | Description |
|---|---|---|
| Daily Queue Reset | Midnight (Cairo time, UTC+2) | Resets all driver queues using ExecuteInTransactionAsync() |
| Trip Auto-Complete | Delayed (per trip) | Marks trip as Completed after estimated travel time |
Each driver's QR code is an AES-GCM encrypted payload containing {DriverId, MicrobusId, RouteId, Expiration}. Staff scan it → decrypt → validate → create trip, all in one atomic operation with 4 SignalR broadcasts.
GPS updates trigger OSRM routing queries. Return trip durations are cached (4 hours TTL) to minimize API calls. Updates under 30m are silently dropped via Haversine formula.
When a bus checks in/out, both departure and arrival route subscriber groups receive live count updates via SignalRRouteTrackingNotificationService.
Deleted accounts have their phone prefixed with DELETED_{userId}_, freeing the number for reuse while preserving audit trail.
Every endpoint returns ApiResponse / ApiResponseWithData<T> through ApiResponseFactory, ensuring consistent error handling across all clients.
ExceptionHandlingMiddleware catches all unhandled exceptions, applies security headers (X-Content-Type-Options, X-XSS-Protection, X-Frame-Options), and returns localized error messages.
sequenceDiagram
participant Backend
participant Sender as WhatsApp Sender<br/>(ASP.NET Core)
participant Service as WhatsApp Service<br/>(Node.js)
participant WhatsApp as WhatsApp Web
Backend->>Sender: POST /api/whatsapp/send {number, message}<br/>Header: x-node-token
Sender->>Sender: Log message to SQL Server
Sender->>Service: POST /send {number, message}
Service->>WhatsApp: client.sendMessage()
WhatsApp-->>Service: Delivery confirmation
Service-->>Sender: 200 OK
Sender->>Sender: Update delivery status
Sender-->>Backend: 200 OK
See whatsapp-sender/README.md, whatsapp-service/README.md, and whatsapp-client/README.md for full documentation.
# docker-compose.yml — 3 services
services:
api: # ASP.NET Core 8 → port 5000
sqlserver: # SQL Server 2022 → port 1433 (healthcheck)
redis: # Redis 7 Alpine → port 6379graph LR
API["smartmicrobus-api (:5000)"] --> SQL["smartmicrobus-db (:1433)"]
API --> RED["smartmicrobus-redis (:6379)"]
SQL ---|healthcheck| SQL
- Docker Desktop
- .NET 8 SDK
- EF Core CLI:
dotnet tool install --global dotnet-ef
git clone https://github.com/Ibrahim-Hassan74/GraduationProject.git
cd GraduationProject
docker-compose up --builddotnet ef database update \
--project SmartMicrobus.Infrastructure \
--startup-project SmartMicrobus.API \
--connection "Server=localhost,1433;Database=SmartMicrobusDb;User Id=sa;Password=YourStrong@Pass123;TrustServerCertificate=True;"Navigate to http://localhost:5000/swagger
| Variable | Default | Description |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Production | Runtime environment |
ConnectionStrings__DefaultConnection |
Docker SQL | SQL Server connection |
ConnectionStrings__RedisConnection |
redis:6379 |
Redis connection |
MSSQL_SA_PASSWORD |
YourStrong@Pass123 |
SQL Server password |
GraduationProject/
├── SmartMicrobus.API/
│ ├── Controllers/ # 11 API controllers (versioned v1)
│ │ ├── Admin/ # AdminController (user/manager management)
│ │ ├── AccountController # Auth (register, login, OTP, JWT refresh)
│ │ ├── DriverController # Driver operations + GPS
│ │ ├── StaffController # QR check-in/out
│ │ ├── ManagerController # Station CRUD + exports + dashboard
│ │ ├── RoutesController # Route CRUD + OSRM queries
│ │ ├── StationsController# Station CRUD + nearest station
│ │ ├── ReportController # Complaint management
│ │ ├── MicrobusController# Microbus queries
│ │ └── FavoriteRoutesController # Passenger favorites
│ ├── Hubs/ # 4 SignalR WebSocket hubs
│ ├── Realtime/ # 4 notification service implementations
│ ├── Middleware/ # ExceptionHandling + HangfireTokenCookie
│ └── Filters/ # CustomAuthorizeFilter
├── SmartMicrobus.Core/
│ ├── Domain/
│ │ ├── Entities/ # 16 domain entities
│ │ ├── IdentityEntities/ # ApplicationUser, ApplicationRole
│ │ └── Options/ # Configuration POCOs
│ ├── Services/ # 10 service implementation directories
│ ├── ServiceContracts/ # 11 interface directories
│ ├── DTO/ # 86 data transfer objects (13 categories)
│ ├── Enums/ # 15 enumerations
│ ├── RepositoryContracts/ # Repository interfaces
│ └── Helper/ # ApiResponse, Pagination, GeoValidator
├── SmartMicrobus.Infrastructure/
│ ├── Repository/ # 17 repository implementations + UnitOfWork
│ └── Data/ # ApplicationDbContext + Migrations
├── Dockerfile # Multi-stage build
├── docker-compose.yml # API + SQL Server + Redis
└── .dockerignore
The system uses 17 specialized repositories coordinated through a Unit of Work:
| Repository | Key Operations |
|---|---|
DriverRepository |
Lookup by license, plate number, station scoping |
MicrobusRepository |
Assignment, pagination with filters |
QueueItemRepository |
Position management, status transitions |
QueueRepository |
Queue creation per route, daily reset |
RouteRepository |
Spatial queries, bidirectional lookup |
TripRepository |
History with date range, driver-scoped queries |
StationRepository |
Nearest station (OSRM), spatial Point storage |
ReportRepository |
Status filtering, station-scoped queries |
FavoriteRouteRepository |
Passenger favorites toggle |
UserRepository |
Paginated user listing, role filtering |
StaffRepository |
Station-scoped staff management |
GenericRepository<T> |
Base CRUD with EF Core Include support |
UnitOfWork |
CompleteAsync() + ExecuteInTransactionAsync() |
This project was built as a graduation project at the Faculty of Computers and Information, Minia University.