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

  1. appsettings.json (Static config):
    - Connection strings (encrypted)
    - JWT settings
    - Security settings
    - AppBasePath (for Swagger in production)

  2. CatAppConfigSetting (Database config):
    - VideoSDK credentials
    - SMTP settings
    - Firebase FCM settings
    - Feature flags

6.2 Environment-Specific Config

  • appsettings.json - Production
  • appsettings.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

  1. Clear Separation of Concerns: Controllers → Repositories → Database
  2. Security-First Design: Multiple validation layers
  3. Multi-Tenancy Support: Organization isolation built-in
  4. Modern .NET: .NET 8.0 with latest packages
  5. API Documentation: Swagger integrated
  6. Encryption Everywhere: IDs, connection strings, sensitive data
  7. Interface-Based Design: Easy to mock for testing
  8. Consistent Response Format: BaseResponse pattern

13. Recommendations

13.1 Short-Term (Do Now)

  1. Remove exception message exposure
  2. Add global exception handler
  3. Remove commented code
  4. Add API versioning (v1)

13.2 Medium-Term (Do Next)

  1. Replace ADO.NET with Entity Framework Core
  2. Add logging framework (Serilog)
  3. Add health check endpoints
  4. Implement retry policies (Polly)
  5. Add API rate limiting

13.3 Long-Term (Plan)

  1. Migrate to microservices architecture
  2. Implement CQRS pattern for complex operations
  3. Add event sourcing for booking history
  4. Implement API gateway (Azure API Management)
  5. 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.