Appearance
System Architecture
High-Level Overview
MedRegia is a modular monolith — a single deployable unit with clearly separated internal modules. The frontend is a Single Page Application (SPA) that communicates exclusively through the REST API.
mermaid
graph TB
subgraph Browser["Browser (Vue 3 SPA)"]
FSD["Feature-Sliced Design layers<br/>pages → widgets → features → shared"]
PERM["usePermission()<br/>JWT claim check"]
end
subgraph API["Backend (.NET 10)"]
MW["JWT Middleware"]
FILTER["RequirePermission Filter"]
subgraph MOD["Modules"]
IDENTITY["Identity<br/>Auth · Users · Roles · RBAC"]
VENDOR["VendorManagement<br/>Vendors · Invoices · Transactions"]
SETTINGS["Settings"]
DASHBOARD["Dashboard"]
REPORTS["Reports"]
ONBOARDING["Onboarding"]
end
DAPPER["Dapper + IDbConnectionFactory"]
AUTOMAPPER["AutoMapper"]
end
subgraph DB["MySQL 8"]
TABLES[("Tables")]
DBUP["DbUp Migrations"]
end
FSD -->|"HTTP + Bearer JWT"| MW
MW --> FILTER
FILTER --> MOD
MOD --> DAPPER
MOD --> AUTOMAPPER
DAPPER --> TABLES
DBUP -.->|"schema versioning"| TABLESRequest Lifecycle
mermaid
sequenceDiagram
participant Vue as Vue SPA
participant Nginx as Nginx
participant API as ASP.NET Core
participant Filter as PermissionFilter
participant Svc as Service
participant DB as MySQL
Vue->>Nginx: GET /api/vendors (Bearer JWT)
Nginx->>API: proxy_pass /api/vendors
API->>Filter: [RequirePermission("Vendor","Read")]
Filter->>Filter: Decode JWT claims
alt is_admin = "true"
Filter-->>API: Bypass all checks
else
Filter->>Filter: Check "Vendor:Read" in perm[] claims
alt Missing permission
Filter-->>Vue: 403 Forbidden (ProblemDetails)
end
end
API->>Svc: GetVendorsPagedAsync(...)
Svc->>DB: SELECT ... FROM vendors
DB-->>Svc: rows
Svc-->>API: (Items, TotalCount)
API-->>Vue: 200 { items, totalCount, page, pageSize }Module Structure
Each module follows the same four-layer layout:
Modules/[ModuleName]/
├── Domain/ ← POCO models (no annotations, no logic)
│ ├── [Entity].cs
│ └── Enums/
├── Data/ ← Dapper repositories (SQL only)
│ ├── Interfaces/
│ └── Repositories/
├── Business/ ← All business logic
│ ├── DTOs/
│ ├── Interfaces/
│ ├── Profiles/ ← AutoMapper
│ └── Services/
└── Controller/ ← HTTP interface (maps DTOs, calls services)Data Serialisation Pipeline
MySQL (snake_case)
↓ Dapper MatchNamesWithUnderscores
C# Domain (PascalCase)
↓ AutoMapper
DTO (PascalCase)
↓ System.Text.Json CamelCase policy
JSON (camelCase)
↓ TypeScript interface
Frontend (camelCase)Example: vendor_id → VendorId (C#) → vendorId (JSON/TS)
Authentication Flow
mermaid
sequenceDiagram
participant U as User (Browser)
participant API as POST /api/auth/login
participant Auth as AuthService
participant DB as MySQL
U->>API: { email, password }
API->>Auth: LoginAsync(email, password)
Auth->>DB: SELECT user WHERE email = ?
DB-->>Auth: user row
Auth->>Auth: BCrypt.Verify(password, hash)
Auth->>DB: SELECT permissions for user
DB-->>Auth: permission rows
Auth->>Auth: Build JWT with perm[] claims
Auth-->>API: { token, expiresAt, user }
API-->>U: 200 LoginResponseDto
U->>U: Store token in localStorage
U->>U: All subsequent requests: Authorization: Bearer <token>RBAC Model
mermaid
graph LR
USER["User"] -->|"has one"| ROLE["Role"]
ROLE -->|"linked to many"| POLICY["Policy"]
USER -->|"also linked directly"| POLICY
POLICY -->|"contains many"| PERM["Permission<br/>(Collection:Action)"]
style PERM fill:#16a34a,color:#fff
style POLICY fill:#d97706,color:#fff
style ROLE fill:#7c3aed,color:#fff
style USER fill:#2563eb,color:#fffPermissions are merged from both the user's role policies and any user-direct policies. Admin users receive is_admin: "true" in their JWT which bypasses all permission checks.
Modules at a Glance
| Module | Route Prefix | Responsibility |
|---|---|---|
| Identity | /api/auth, /api/users, /api/roles, /api/permissions, /api/policies | Authentication, users, RBAC |
| VendorManagement | /api/vendors, /api/invoices, /api/transactions | Core business domain |
| Settings | /api/settings | App-wide key-value config |
| Dashboard | /api/dashboard | Read-only aggregate queries |
| Reports | /api/reports | Excel export endpoints |
| Onboarding | /api/onboarding | First-run setup (no auth required) |
| UserProfile | /api/profile | Authenticated user's own profile |