Tahoon API - Structure Analysis¶
Executive Summary¶
The Tahoon API (Psyter Shared API) is a well-structured .NET 8.0 Web API following the Repository Pattern with clear separation of concerns. It serves as a B2B integration gateway for external organizations to access Psyter’s telemedicine services.
Architecture Highlights:
- Clean layered architecture (Controllers → Repositories → Database)
- Strong security focus with multiple validation layers
- Modern .NET 8.0 implementation
- RESTful API design with Swagger documentation
- Multi-database support (Psyter + Scheduling databases)
1. Architecture Overview¶
1.1 Architectural Pattern¶
Three-Tier Architecture with Repository Pattern
┌─────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Controllers + Action Filters) │
│ - AuthController │
│ - UserController │
│ - CareProviderController │
│ - SessionBookingController │
└─────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Business Logic Layer │
│ (Helpers + Validators) │
│ - SecurityHelper (encryption/hashing) │
│ - VideoSDKHelper (video meetings) │
│ - FCMNotificationHelper (push notifications) │
│ - XmlHelper (serialization) │
└─────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Data Access Layer │
│ (Repositories + BaseRepository) │
│ - IAuthRepository / AuthRepository │
│ - IUserRepository / UserRepository │
│ - ICareProviderRepository / CareProviderRepo │
│ - ISchedulingRepository / SchedulingRepo │
│ - ISessionBookingRepository / SessionBooking │
│ - ICommonRepository / CommonRepository │
└─────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Data Storage │
│ - PsyterDatabase (Main DB) │
│ - SchedulingDatabase (Scheduling DB) │
│ (SQL Server with Stored Procedures) │
└─────────────────────────────────────────────────┘
1.2 Cross-Cutting Concerns¶
Action Filters (Applied via attributes/middleware):
- ValidateSecureHashAttribute - Request integrity validation
- ValidateAntiXSSAttribute - XSS attack prevention
- EncryptedModelBinder - Automatic ID decryption
- AuthOperationFilter - Swagger authentication UI
Extensions:
- DataRowExtensions - ADO.NET to POCO mapping
- CryptographyStatic - Static encryption utilities
2. Component Deep Dive¶
2.1 Controllers Layer¶
AuthController¶
Purpose: OAuth 2.0 token generation for organization authentication
Endpoints:
- POST /api/auth/token - Generate JWT access token
Flow:
Client → [POST grant_type + access_key]
↓
AuthRepository.Authenticate(accessKey)
↓
Validate organization credentials
↓
Generate JWT with claims:
- OrganizationId
- SharedAPIKey
↓
Return access_token (24hr expiry)
Key Logic:
- Validates grant_type=password
- Queries organization by AccessKey
- Creates JWT with OrganizationId and SharedAPIKey claims
- 24-hour token expiration
UserController¶
Purpose: User registration and mental health assessment management
Endpoints:
1. POST /api/user/register - Register organization user
2. GET /api/user/getassessmentquestions - Get screening questions
3. POST /api/user/submituserassessmentquestions - Submit assessment answers
Key Features:
- Multi-tenant validation: Ensures user belongs to requesting organization
- Secure hash validation: Request integrity checks
- XSS protection: Input sanitization
- Assessment flow: Mental health screening questionnaire
Registration Flow:
Client Request
↓
[ValidateSecureHash] - Verify request integrity
↓
Extract OrganizationId from JWT
↓
UserRepository.RegisterOrganizationUser()
↓
Store user with OrganizationId mapping
↓
Return encrypted UserLoginInfoId
CareProviderController¶
Purpose: Care provider discovery and scheduling
Endpoints:
1. POST /api/careprovider/getcareproviderslistwithschedule - Search providers with availability
2. POST /api/careprovider/getcareproviderschedule - Get provider schedule details
3. POST /api/careprovider/getcareprovidersprofiledata - Get provider profile
4. GET /api/careprovider/getcataloguedataforfilters - Get filter metadata
Complex Logic - Provider Search:
Flow:
1. Parse filter criteria (gender, specialty, language, etc.)
2. If scheduleDate provided:
a. Call SchedulingRepository.GetNextHourlyScheduleForCareProviders()
b. Get available provider IDs + time slots
3. Call CareProviderRepository with filter criteria
4. Merge provider data with availability
5. Build full name (PLang ↔ SLang fallback)
6. Return enriched provider list
Multi-language Support:
- Primary Language (PLang) vs Secondary Language (SLang)
- Fallback mechanism: PLang ?? SLang
Schedule Integration:
- Real-time availability from SchedulingDatabase
- Hourly slots or custom slot durations
- Timezone-aware scheduling
SessionBookingController¶
Purpose: Session booking lifecycle (create, cancel)
Endpoints:
1. POST /api/sessionbooking/booksession - Book therapy session
2. POST /api/sessionbooking/cancelbooking - Cancel booking
Booking Flow (Most Complex):
1. Validate Request
↓
2. User Registration/Validation
- If userId missing: auto-register guest user
- If userId present: validate ownership
↓
3. Setup Booking Data
- Build XML booking structure
- Set charity organization flags
↓
4. Validate Slot Availability
- Call SchedulingRepository.GetScheduleByHour()
- Check slot not already booked
↓
5. Save Booking (Two-phase)
Phase A: SchedulingRepository.SaveScheduleBooking()
→ Get SlotBookingId
Phase B: SessionBookingRepository.SaveBookingOrderPayForData()
→ Get OrderId
↓
6. Create Video Meeting
- VideoSDKHelper.CreateAndSaveVideoSDKMeetingId()
- Store meetingId in database
↓
7. Update Booking Status
- If successful: Status = Confirmed (1)
- If failed: Status = Cancelled (8)
↓
8. Send Notifications
- FCMNotificationHelper to care provider
- Email notifications (commented out)
↓
9. Return Response
- meetingId (VideoSDK room)
- encrypted bookingId
Cancellation Flow:
1. Validate booking ownership
↓
2. Get booking details (refund calculation)
↓
3. Process refund (charity org balance restore)
↓
4. Update booking status to Cancelled (3)
↓
5. Return confirmation
Key Business Rules:
- Charity organizations: Free bookings (balance-based quota)
- Slot validation: Real-time availability check before booking
- Automatic video room creation for all bookings
- Dual database updates (Scheduling + Psyter DBs)
2.2 Repository Layer¶
BaseRepository¶
Purpose: Shared database access infrastructure
Key Methods:
- CreateDbConnection(dbKey) - Create SQL connection with decryption
- CreateDbCommand(procedureName, connection) - Create stored procedure command
- DecryptConnectionString(encrypted) - Decrypt connection string segments
Connection String Decryption:
Encrypted format in appsettings.json:
"Data Source=<encrypted>;Initial Catalog=<encrypted>;User Id=<encrypted>;Password=<encrypted>"
Decryption process:
1. Split by ';'
2. For each segment: decrypt value if key matches sensitive fields
3. Reconstruct connection string
Timeout Configuration:
- Default: 30 seconds
- Configurable via CommandTimeout in appsettings
AuthRepository¶
Interface: IAuthRepository
Method: Authenticate(string appToken)
Implementation:
Stored Procedure: Organization_Authenticate_ByApplicationToken
Input: @ApplicationToken
Output: OrganizationId, SharedAPIKey
Returns: OrgAuthResponse
UserRepository¶
Interface: IUserRepository
Key Methods:
1. RegisterOrganizationUser(request, organizationId) - User registration
2. ValidateOrganizationUserId(userId, organizationId) - Ownership validation
3. GetScreeningQuestion() - Assessment questions
4. SubmitUserAssessmentQuestions(request) - Assessment answers
Multi-tenant Enforcement:
Every method validates OrganizationId to prevent cross-organization data access.
CareProviderRepository¶
Interface: ICareProviderRepository
Key Methods:
1. GetCareProvidersListForFilterCriteriaForGuest() - Anonymous search
2. GetCareProvidersListForFilterCriteriaForLoginUser() - Authenticated search
3. GetCareProvidersProfileData() - Provider profile details
4. CatalogueDataForCareProvidersListFilters() - Filter metadata
Filter Criteria:
- Gender type
- Language IDs
- Specialty IDs
- Experience level
- Service provider level
SchedulingRepository¶
Interface: ISchedulingRepository
Key Methods:
1. GetNextHourlyScheduleForCareProviders(request) - Bulk availability check
2. GetScheduleByHour(request) - Single slot validation
3. SaveScheduleBooking(xmlData) - Create booking
4. UpdateBookingStatus(xmlData) - Status transitions
XML-based Data Structures:
Uses XML serialization for complex nested objects (legacy stored procedure design).
SessionBookingRepository¶
Interface: ISessionBookingRepository
Key Methods:
1. SaveBookingOrderPayForData(xml) - Save order/payment record
2. GetBookingDetailsForRefund(request) - Cancellation data
3. RefundBookingPayment(request) - Process refund
4. GeBookingDetails(orderMainId) - Booking notification data
Payment Integration:
- Charity organization balance tracking
- Refund processing
- Wallet payment support
CommonRepository¶
Interface: ICommonRepository
Key Methods:
1. GetUserRemindersList(userId, bookingId) - Notification reminders
2. SaveAppointmentMeetingId(bookingId, userId, meetingId) - Video meeting link
3. GetAppConfigSettingsByGroupId(groupId) - Configuration settings
Purpose: Shared utilities across modules
2.3 Helper Services¶
SecurityHelper¶
Purpose: Encryption, hashing, and ID obfuscation
Key Methods:
| Method | Purpose | Algorithm |
|---|---|---|
GeneratePassword(password) |
Password hashing | SHA-256 |
ValidatePassword(entered, stored) |
Password verification | SHA-256 compare |
EncryptAES(plainText) |
Symmetric encryption | AES-256-CBC |
DecryptAES(cipherText) |
Symmetric decryption | AES-256-CBC |
EncryptId(text) |
ID encryption for transit | AES + Base64URL |
DecryptId(text) |
ID decryption | Base64URL + AES |
EncryptString(text) |
String encryption (legacy) | Rijndael (AesManaged) |
DecryptString(text) |
String decryption (legacy) | Rijndael (AesManaged) |
Slugify(input) |
URL-safe strings | Regex normalization |
Encryption Stack:
AES-256-CBC
├── Key derivation: Rfc2898DeriveBytes (PBKDF2)
├── Key size: 256 bits
├── Iterations: 2
├── Salt: Configurable (16 chars)
└── IV: Configurable (16 bytes)
ID Encryption Flow:
Long ID (e.g., 12345)
↓
Convert to string
↓
AES-256 Encrypt (using PassPhrase)
↓
Base64 encode
↓
URL-safe Base64 (replace +/= with -_<empty>)
↓
Encrypted ID: "Kx3mF-2pL_9qR"
VideoSDKHelper¶
Purpose: Video conferencing integration (VideoSDK.live)
Key Methods:
1. GenerateToken(apiKey, secretKey) - JWT for VideoSDK API
2. CreateMeetingAsync(token, customMeetingId) - Create meeting room
3. CreateAndSaveVideoSDKMeetingId(userId, bookingId) - Auto-create & persist
4. ValidateMeetingAsync(token, meetingId) - Check meeting status
5. GetSessionRecording(meetingId) - Retrieve recordings
6. SetVideoSDKConfiguration() - Load config from database
Configuration Sources:
- Loaded from CatAppConfigSetting table (GroupId = 25)
- Settings:
- VIDEOSDK_API_ENDPOINT
- VIDEOSDK_API_KEY
- VIDEOSDK_SECRET_KEY
- VIDEO_SDK_ENABLE_RECORDING (true/false)
Meeting Creation Flow:
1. Generate VideoSDK JWT token
- Permissions: allow_join, allow_mod
- Expiry: 24 hours
↓
2. POST to /rooms endpoint
- Optional: customRoomId
- Optional: autoStartConfig (recording + transcription)
↓
3. Receive roomId from API
↓
4. Save to database (AppointmentMeetingLink table)
↓
5. Return roomId to client
Recording Support:
- Auto-start recording if enabled
- Transcription with AI summary
- Grid layout (2 participants)
FCMNotificationHelper¶
Purpose: Firebase Cloud Messaging push notifications
Key Concepts:
- Uses Firebase Admin SDK
- Topic-based messaging (Android/iOS separate)
- Notification templates with data payload
Topic Naming Convention:
For care providers:
- Android: "android_doctor_{userLoginInfoId}~"
- iOS: "ios_doctor_{userLoginInfoId}~"
For patients:
- Android: "android_patient_{userLoginInfoId}~"
- iOS: "ios_patient_{userLoginInfoId}~"
Notification Types (Enumeration):
1. ProfileUpdate
2. NewBooking ← Used in booking flow
3. PayslipGenerated
4. HomeWorkAssigned
5. ReserveSlot
6. Referral
7. CancelRefundBooking
8. PsyterSupportNotification
9. SCHFSExpiry
10. RefundByAdmin
11. CommonNotification
12. ReminderNotification
13. OtherPartyJoined
14. ProviderSignedContract
15. NewMessage
16. AccountDeleted
XmlHelper¶
Purpose: XML serialization/deserialization for stored procedures
Why XML?:
Legacy stored procedures accept complex nested structures as XML parameters.
Usage Example:
var bookingData = new AvailabilityBookingRequest { ... };
var xml = XmlHelper.ObjectToXml(bookingData);
var response = schedulingRepository.SaveScheduleBooking(xml);
2.4 Action Filters¶
ValidateSecureHashAttribute¶
Purpose: Request integrity verification using HMAC-SHA256
How It Works:
1. Extract SharedAPIKey from JWT claims
2. Identify properties with [IncludeInHash] attribute
3. Concatenate values (alphabetically by property name)
4. Generate HMAC-SHA256 hash using SharedAPIKey
5. Compare with secureHash field in request
6. Reject if mismatch (401 Unauthorized)
Example:
public class BookOrderRequest
{
[IncludeInHash]
public string UserId { get; set; }
[IncludeInHash]
public string CareProviderId { get; set; }
[IncludeInHash]
public string SlotDate { get; set; }
public string SecureHash { get; set; } // Calculated by client
}
Hash = HMAC-SHA256(
key: SharedAPIKey,
data: CareProviderId + SlotDate + UserId // Alphabetically sorted
)
ValidateAntiXSSAttribute¶
Purpose: Prevent XSS attacks via input validation
Detection Patterns:
- Script tags: <script>, </script>, <iframe>, etc.
- Event handlers: onclick=, onerror=, etc.
- JavaScript protocols: javascript:, data:text/html
- VBScript/LiveScript protocols
Two Modes:
1. AntiXssAttribute: Strict (no HTML allowed)
2. AntiXssAllowHTMLAttribute: Permissive (safe HTML allowed)
Implementation:
Regex pattern detects dangerous patterns
↓
If match found:
→ Return "XSS content detected"
↓
If no match:
→ ValidationResult.Success
EncryptedModelBinder¶
Purpose: Automatic decryption of encrypted IDs
Process:
1. Request body parsed as JSON
↓
2. For each property with [Encrypted] attribute:
- Store encrypted value in property
- Decrypt value
- Store decrypted long in {PropertyName}_Decrypted
↓
3. Controller receives both encrypted + decrypted values
Example:
public class BookOrderRequest
{
[Encrypted]
public string UserId { get; set; } // Encrypted value stored here
public long UserId_Decrypted { get; set; } // Auto-populated by binder
}
SwaggerActionFilter (AuthOperationFilter)¶
Purpose: Add authentication to Swagger UI for [Authorize] endpoints
Effect:
- Adds lock icon to protected endpoints
- Prompts for bearer token
- Auto-includes Authorization header
2.5 Data Models¶
Request Models¶
AuthRequest:
- GrantType - “password”
- AccessKey - Organization API key
UserRegistrationRequest:
- ReferenceId - Organization’s user ID
- Name - Full name
- DOB - Date of birth
- GenderType - Male/Female enum
- SecureHash - HMAC-SHA256 hash
CareProviderFilterCriteria:
- UserId - Optional (guest vs authenticated)
- ScheduleDate - Filter by availability
- GMTTimeDiffrenceHours - Timezone offset
- ApplyFilter - Enable/disable filtering
- GenderType, LanguageIds, SpecialtyIds, etc.
BookOrderRequest:
- UserId, CareProviderId (encrypted)
- SlotDate, SlotStartTime, SlotEndTime
- ApplicationMultiSlotId - Service type ID
- CatCommunicationTypeId - Video/Audio/Text
- IsBookingFromMobile, BookingPlatformId
- SecureHash
Response Models¶
BaseResponse:
public class BaseResponse
{
public int Status { get; set; } // 0 = error, 1 = success
public string Message { get; set; } // Human-readable message
public ResponseReason Reason { get; set; } // Enum error code
public dynamic Data { get; set; } // Response payload
}
CareProvidersListResponse:
- CareProvidersList - Array of provider objects
- Each provider:
- UserLoginInfoId (encrypted)
- FullName (PLang ↔ SLang)
- GenderType
- AvailableScheduleHoursList - Time slots
- AvailableSlotsList - Detailed slots
- SlotDurationType - Slot duration setting
HourlyScheduleResponseWrapper:
- AvailableHoursList - Available hours
- AvailableHoursSlots - Detailed slot objects
- SlotDurationObj - Duration settings per provider
- AvailableServiceProvidersList - CSV of provider IDs
2.6 Extensions¶
DataRowExtensions¶
Purpose: Convert ADO.NET DataRow/DataTable to POCOs
Key Methods:
- ToObject<T>(DataRow) - Single object mapping
- ToList<T>(DataTable) - List mapping
- Fill<T>(DataRow, ref T) - In-place object population
- ToDataTable<T>(IList<T>) - Reverse mapping
- DeepCopy<T>() - Object cloning via JSON serialization
Special Feature - Encrypted Attribute Handling:
If property has [Encrypted] attribute:
1. Read long value from DataRow
2. Encrypt using CryptographyStatic.EncryptId()
3. Store encrypted string in property
Example:
// Database returns UserLoginInfoId = 12345
// Model property has [Encrypted] attribute
// Result: UserLoginInfoId = "Kx3mF-2pL_9qR" (encrypted)
CryptographyStatic¶
Purpose: Static encryption utilities (no DI required)
Configuration Loading:
- Reads appsettings.json directly (no DI)
- Used in static contexts (DataRowExtensions)
Methods Mirror SecurityHelper:
- EncryptId(text) / DecryptId(text)
- ToUrlSafeBase64() / FromUrlSafeBase64()
- Encrypt<T>() / Decrypt<T>()
3. Data Flow Examples¶
3.1 Complete Booking Flow¶
┌──────────────────────────────────────────────────────────────┐
│ Client: POST /api/sessionbooking/booksession │
└──────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Action Filter: ValidateAntiXSS │
│ - Check for XSS patterns in input │
└──────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Action Filter: ValidateSecureHash │
│ - Extract SharedAPIKey from JWT │
│ - Calculate HMAC-SHA256 hash │
│ - Compare with request.SecureHash │
└──────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Model Binder: EncryptedModelBinder │
│ - Decrypt UserId → UserId_Decrypted │
│ - Decrypt CareProviderId → CareProviderId_Decrypted │
│ - Decrypt other encrypted IDs │
└──────────────────┬───────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Controller: SessionBookingController.BookSession() │
└──────────────────┬───────────────────────────────────────────┘
│
├──► Step 1: Validate User
│ ┌────────────────────────────────────┐
│ │ If UserId_Decrypted == 0: │
│ │ → UserRepository.Register() │
│ │ Else: │
│ │ → UserRepository.Validate() │
│ └────────────────────────────────────┘
│
├──► Step 2: Setup Booking Data
│ ┌────────────────────────────────────┐
│ │ Build BookingOrderPayForData │
│ │ Build AvailabilityBookingRequest │
│ │ Set charity org flags │
│ └────────────────────────────────────┘
│
├──► Step 3: Validate Slot
│ ┌────────────────────────────────────┐
│ │ SchedulingRepo.GetScheduleByHour() │
│ │ - Check slot available │
│ │ - Get ScheduleId │
│ └────────────────────────────────────┘
│
├──► Step 4: Save Booking (Phase 1)
│ ┌────────────────────────────────────┐
│ │ XmlHelper.ObjectToXml() │
│ │ SchedulingRepo.SaveScheduleBooking()│
│ │ → Returns SlotBookingId │
│ └────────────────────────────────────┘
│
├──► Step 5: Save Order (Phase 2)
│ ┌────────────────────────────────────┐
│ │ SessionBookingRepo.Save...() │
│ │ → Returns OrderId │
│ └────────────────────────────────────┘
│
├──► Step 6: Create Video Meeting
│ ┌────────────────────────────────────┐
│ │ VideoSDKHelper.CreateAndSave...() │
│ │ ├─► Generate JWT token │
│ │ ├─► POST to VideoSDK API │
│ │ ├─► Get meetingId/roomId │
│ │ └─► Save to database │
│ └────────────────────────────────────┘
│
├──► Step 7: Update Booking Status
│ ┌────────────────────────────────────┐
│ │ If OrderId > 0: │
│ │ → Status = Confirmed (1) │
│ │ Else: │
│ │ → Status = Cancelled (8) │
│ │ XmlHelper + UpdateBookingStatus() │
│ └────────────────────────────────────┘
│
├──► Step 8: Send Notifications
│ ┌────────────────────────────────────┐
│ │ If OrderId > 0: │
│ │ FCMHelper.SendFCMMessage() │
│ │ - To care provider (iOS/Android) │
│ │ - Include booking details │
│ └────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Response: BaseResponse │
│ { │
│ status: 1, │
│ data: { │
│ meetingId: "abc-123", │
│ bookingId: "<encrypted>" │
│ } │
│ } │
└──────────────────────────────────────────────────────────────┘
3.2 Authentication Flow¶
┌────────────────────────┐
│ Client │
│ POST /api/auth/token │
│ grant_type=password │
│ access_key=ORG-KEY-123 │
└───────────┬────────────┘
│
▼
┌─────────────────────────────────────────┐
│ AuthController.GetToken() │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ AuthRepository.Authenticate(access_key) │
│ - SP: Organization_Authenticate_... │
│ - SELECT OrganizationId, SharedAPIKey │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ If valid organization: │
│ Create JWT with claims: │
│ - Sub: OrganizationId │
│ - OrganizationId: {id} │
│ - SharedAPIKey: {base64-key} │
│ - Jti: {guid} │
│ Expiry: UtcNow + 24 hours │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Response: │
│ { │
│ "access_token": "eyJhbGci...", │
│ "token_type": "bearer", │
│ "expires_in": 86400 │
│ } │
└─────────────────────────────────────────┘
3.3 Provider Search with Schedule¶
┌─────────────────────────────────────────┐
│ POST /api/careprovider/get...schedule │
│ { │
│ userId: "encrypted-or-empty", │
│ scheduleDate: "2025-11-15", │
│ filterCriteria: { ... } │
│ } │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ If scheduleDate != null: │
│ SchedulingRepo.GetNextHourly...() │
│ ↓ │
│ Returns: │
│ - AvailableServiceProvidersList (CSV) │
│ - AvailableHoursList (per provider) │
│ - AvailableSlots (detailed) │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ If userId == 0 (guest): │
│ CareProviderRepo.Get...ForGuest() │
│ Else: │
│ Validate user ownership │
│ CareProviderRepo.Get...ForLoginUser() │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Merge provider data with schedules │
│ For each provider: │
│ - Match UserLoginInfoId │
│ - Attach AvailableScheduleHoursList │
│ - Attach AvailableSlotsList │
│ - Build FullName (PLang ↔ SLang) │
└───────────┬─────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Response: CareProvidersListResponse │
│ { │
│ careProvidersList: [ │
│ { │
│ userLoginInfoId: <encrypted>, │
│ fullName: "Dr. Sarah Ahmed", │
│ availableScheduleHoursList: [...],│
│ availableSlotsList: [...] │
│ } │
│ ] │
│ } │
└─────────────────────────────────────────┘
4. Database Interaction¶
4.1 Database Schema (Conceptual)¶
PsyterDatabase (Main):
Tables (inferred from code):
- Organization
- OrganizationId (PK)
- ApplicationToken
- SharedAPIKey (Base64)
- UserLoginInfo
- UserLoginInfoId (PK)
- OrganizationId (FK)
- ReferenceId (Organization's user ID)
- Name, DOB, GenderType
- CareProvider (extends UserLoginInfo)
- FirstNamePLang, FirstNameSLang
- MiddleNamePLang, MiddleNameSLang
- LastNamePLang, LastNameSLang
- GenderType
- Specialty, Languages, etc.
- OrderMain
- OrderMainId (PK)
- UserLoginInfoConsumer (FK)
- UserLoginInfoPhysician (FK)
- SlotBookingId (FK)
- Amount, TotalAmount
- CharityOrganizationId (FK)
- AppointmentMeetingLink
- BookingId (FK)
- MeetingId (VideoSDK roomId)
- CreatedBy, CreatedDate
- CatAppConfigSetting
- CatAppConfigSettingGroupId (FK)
- PropertyName, PropertyValue
- TitlePLang, TitleSLang
SchedulingDatabase:
Tables (inferred):
- ServiceProviderAvailability
- AvailabilityId (PK)
- ServiceProviderId (FK)
- AvailabilityDate
- ServiceProviderSchedule
- ScheduleId (PK)
- ServiceProviderId (FK)
- ScheduleDate, ScheduleHour
- SlotBooking
- SlotBookingId (PK)
- ScheduleId (FK)
- ServiceConsumerId (FK)
- BookingStatusId (FK)
- StartTime, EndTime
- CatBookingStatus
- BookingStatusId (PK)
- StatusName
- (1=Confirmed, 3=Cancelled, 6=Pending, 8=Failed)
4.2 Stored Procedures (Examples)¶
Based on code references:
-- Authentication
Organization_Authenticate_ByApplicationToken
@ApplicationToken NVARCHAR(MAX)
RETURNS: OrganizationId, SharedAPIKey
-- User Management
OrganizationUser_Register
@OrganizationId BIGINT,
@ReferenceId NVARCHAR(100),
@Name NVARCHAR(200),
@DOB DATE,
@GenderType INT
RETURNS: UserLoginInfoId
OrganizationUser_Validate
@UserLoginInfoId BIGINT,
@OrganizationId BIGINT
RETURNS: Status (1=valid, 0=invalid)
-- Care Providers
CareProvider_GetListForGuest
@FilterXml XML,
@AvailableProviderIds NVARCHAR(MAX),
@OrganizationId BIGINT
RETURNS: Provider details
-- Scheduling
SchedulingRepository.GetNextHourlySchedule
@ApplicationId INT,
@ServiceProviderIdsList NVARCHAR(MAX),
@ScheduleDate DATETIME,
@GMTTimeDifference INT
RETURNS: Availability data (XML or dataset)
SchedulingRepository.SaveScheduleBooking
@BookingXml XML
RETURNS: BookingId, RejectCode (if slot taken)
SchedulingRepository.UpdateBookingStatus
@StatusUpdateXml XML
RETURNS: Success/failure
-- Session Booking
SessionBooking_SaveOrderPayForData
@BookingDataXml XML
RETURNS: OrderId, SlotBookingId, Status
SessionBooking_GetDetailsForRefund
@BookingId BIGINT,
@PatientId BIGINT,
@CareProviderId BIGINT
RETURNS: Order details
SessionBooking_RefundPayment
@OrderId BIGINT,
@RefundAmount DECIMAL,
@CharityOrganizationId BIGINT
RETURNS: Status
-- Common
Common_SaveAppointmentMeetingId
@BookingId BIGINT,
@UserLoginInfoId BIGINT,
@MeetingId NVARCHAR(200)
Common_GetAppConfigSettings
@GroupId INT
RETURNS: Configuration settings
5. Security Architecture¶
5.1 Security Layers¶
┌────────────────────────────────────────────────────┐
│ Layer 1: Transport Security │
│ - HTTPS required │
│ - TLS 1.2+ │
└────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Layer 2: Authentication │
│ - JWT Bearer Tokens │
│ - 24-hour expiration │
│ - OrganizationId + SharedAPIKey claims │
└────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Layer 3: Request Integrity │
│ - SecureHash (HMAC-SHA256) │
│ - SharedAPIKey as signing key │
│ - Tamper detection │
└────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Layer 4: Input Validation │
│ - Anti-XSS filters │
│ - Model validation │
│ - Parameter sanitization │
└────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Layer 5: Data Encryption │
│ - ID obfuscation (AES-256) │
│ - Encrypted connection strings │
│ - Sensitive data encrypted at rest │
└────────────────┬───────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Layer 6: Multi-Tenancy Isolation │
│ - OrganizationId validation on every query │
│ - Row-level security via stored procedures │
│ - Prevent cross-organization data access │
└────────────────────────────────────────────────────┘
5.2 Encryption Key Hierarchy¶
Master Keys (appsettings.json):
├── JWT:Key (HMAC signing key)
├── SecuritySettings:PassPhrase (AES master key)
├── SecuritySettings:Salt (KDF salt)
└── SecuritySettings:Vector (AES IV)
Derived Keys:
├── PBKDF2(PassPhrase, Salt, 2 iterations) → AES-256 key
└── Used for:
├── ID encryption (UserId, ProviderId, BookingId)
├── Connection string decryption
└── String encryption (legacy Cryptography class)
Per-Organization Keys:
└── SharedAPIKey (Base64, unique per org)
└── Used for: SecureHash validation (HMAC-SHA256)
6. Configuration Management¶
6.1 Configuration Sources¶
-
appsettings.json (Static config):
- Connection strings (encrypted)
- JWT settings
- Security settings
- AppBasePath (for Swagger in production) -
CatAppConfigSetting (Database config):
- VideoSDK credentials
- SMTP settings
- Firebase FCM settings
- Feature flags
6.2 Environment-Specific Config¶
appsettings.json- Productionappsettings.Development.json- Development (overrides)
Key Differences:
- Logging levels
- Swagger enabled/disabled
- Connection strings
- External API endpoints
7. External Integrations¶
7.1 VideoSDK.live¶
API Endpoint: Configurable (from database)
Authentication: JWT (custom generation)
Operations:
- Create meeting room
- Validate meeting
- Retrieve recordings
- Start/stop recording
Data Flow:
Tahoon API
↓ (HTTPS)
VideoSDK API (https://api.videosdk.live)
↓
VideoSDK Infrastructure
↓
Client SDK (Android/iOS/Web)
7.2 Firebase Cloud Messaging (FCM)¶
SDK: Google.Apis.FirebaseCloudMessaging.v1
Authentication: Service account JSON (firebase-adminsdk-live.json)
Notification Flow:
Booking Created
↓
FCMNotificationHelper.SendFCMMessage()
↓
Firebase API
↓
Topic: "android_doctor_{providerId}~"
Topic: "ios_doctor_{providerId}~"
↓
Care Provider Device
7.3 SQL Server¶
Databases: 2 separate databases
Connection: ADO.NET (SqlClient)
Pattern: Stored procedures only (no inline SQL)
Security: Encrypted connection strings
8. API Versioning & Evolution¶
Current State: No explicit versioning
Endpoint Structure: /api/{controller}/{action}
Breaking Change Risk: Medium (direct endpoint coupling)
Recommendation: Implement versioning (/api/v1/...)
9. Code Organization Patterns¶
9.1 Dependency Injection¶
Registered Services (Program.cs):
// Configuration
builder.Services.Configure<SecuritySettings>(...)
// Helpers
builder.Services.AddScoped<Cryptography>()
builder.Services.AddScoped<SecurityHelper>()
builder.Services.AddScoped<XmlHelper>()
builder.Services.AddScoped<VideoSDKHelper>()
builder.Services.AddScoped<FCMNotificationHelper>()
// Repositories
builder.Services.AddScoped<IAuthRepository, AuthRepository>()
builder.Services.AddScoped<IUserRepository, UserRepository>()
builder.Services.AddScoped<ISchedulingRepository, SchedulingRepository>()
builder.Services.AddScoped<ICareProviderRepository, CareProviderRepository>()
builder.Services.AddScoped<ISessionBookingRepository, SessionBookingRepository>()
builder.Services.AddScoped<ICommonRepository, CommonRepository>()
Lifetimes:
- All services: Scoped (per HTTP request)
- No Singletons (stateless design)
9.2 Error Handling¶
Pattern: Try-Catch at controller level
Response: Always return BaseResponse
Example:
try
{
// Business logic
return StatusCode(200, new BaseResponse { Status = 1, ... });
}
catch (Exception ex)
{
return StatusCode(500, new BaseResponse {
Status = 0,
Reason = ResponseReason.Error,
Message = ex.Message
});
}
Issue: Exceptions leak to client (potential information disclosure)
10. Design Patterns Used¶
| Pattern | Implementation | Location |
|---|---|---|
| Repository | Interface + Concrete class | Data/Repositories |
| Dependency Injection | Constructor injection | All services |
| DTO | Request/Response models | Data/Model |
| Action Filter | Request validation | ActionFilters |
| Model Binder | Custom deserialization | EncryptedModelBinder |
| Extension Methods | DataRow mapping | Extensions |
| Helper/Utility | Security, XML, Video SDK | Helpers |
| Base Class | Shared DB logic | BaseRepository |
| Strategy (partial) | Guest vs Login user search | CareProviderRepository |
11. Technical Debt & Architecture Issues¶
11.1 Database Access¶
Issue: Direct ADO.NET with stored procedures
Impact:
- Hard to unit test
- Tight coupling to SQL Server
- No LINQ/query flexibility
- Manual DataRow mapping
Modern Alternative: Entity Framework Core
11.2 XML Serialization¶
Issue: Complex objects serialized to XML for stored procedures
Impact:
- Performance overhead
- Verbose code
- Hard to debug
- Legacy approach
Modern Alternative: JSON parameters (if DB supports) or EF Core
11.3 Static Encryption¶
Issue: CryptographyStatic reads appsettings.json directly
Impact:
- Hard to test
- Violates DI principles
- Couples to file system
Fix: Inject IOptions<SecuritySettings> everywhere
11.4 Exception Handling¶
Issue: Raw exceptions returned to client
Example:
catch (Exception ex)
{
return StatusCode(500, ex.Message); // Exposes stack traces
}
Risk: Information disclosure
Fix: Generic error messages, log details server-side
11.5 Commented-Out Code¶
Locations:
- Program.cs - Commented filters
- SessionBookingController - Commented email sending
Impact: Unclear intended behavior
Fix: Remove or implement properly
12. Strengths¶
- Clear Separation of Concerns: Controllers → Repositories → Database
- Security-First Design: Multiple validation layers
- Multi-Tenancy Support: Organization isolation built-in
- Modern .NET: .NET 8.0 with latest packages
- API Documentation: Swagger integrated
- Encryption Everywhere: IDs, connection strings, sensitive data
- Interface-Based Design: Easy to mock for testing
- Consistent Response Format:
BaseResponsepattern
13. Recommendations¶
13.1 Short-Term (Do Now)¶
- Remove exception message exposure
- Add global exception handler
- Remove commented code
- Add API versioning (v1)
13.2 Medium-Term (Do Next)¶
- Replace ADO.NET with Entity Framework Core
- Add logging framework (Serilog)
- Add health check endpoints
- Implement retry policies (Polly)
- Add API rate limiting
13.3 Long-Term (Plan)¶
- Migrate to microservices architecture
- Implement CQRS pattern for complex operations
- Add event sourcing for booking history
- Implement API gateway (Azure API Management)
- Add comprehensive integration tests
Conclusion¶
The Tahoon API demonstrates a solid foundation with strong security practices and clear architectural patterns. The repository pattern with stored procedures provides good separation of concerns, though it introduces some technical debt around testability and modern ORM features.
Key Takeaway: This is a production-ready B2B integration API with robust multi-tenancy and security, but would benefit from modernization of the data access layer and enhanced error handling.