# Quran App — Full System Architecture --- ## 1. System Overview Quran App is a full-stack web platform that connects students with certified Quran teachers (Sheikhs) for live online recitation sessions. It also provides AI-powered solo practice where students record their recitation and receive automated evaluation. The system has three user roles: - **Student** — finds a sheikh, books sessions, practices solo, tracks progress - **Sheikh** — manages availability, accepts session requests, conducts live sessions, reviews students - **Admin** — approves sheikh applications, manages users, monitors the platform ### High-Level System Diagram ``` ┌──────────────────────────────────────────────────────────────────────┐ │ Browser (Client) │ │ │ │ React 19 + TypeScript + Vite │ │ Tailwind CSS + Radix UI (shadcn) │ │ React Router v7 · i18next (AR/EN) · Stripe.js │ │ STOMP/SockJS Client · WebRTC (RTCPeerConnection) │ └───────────────────────┬──────────────────────────────────────────────┘ │ HTTPS REST + WebSocket (WS) ▼ ┌──────────────────────────────────────────────────────────────────────┐ │ Spring Boot 4 Backend │ │ (Java 21) │ │ │ │ REST API (port 8080) │ │ Spring Security + JWT + Google OAuth2 │ │ Spring Data JPA · WebSocket (STOMP broker) │ │ JavaMail · Stripe SDK · JAVE2 (audio) │ └─────────────┬──────────────────────┬────────────────────────────────┘ │ │ ▼ ▼ ┌─────────────────────┐ ┌──────────────────────────────────────────┐ │ PostgreSQL DB │ │ External Services │ │ │ │ │ │ Users, Sessions, │ │ ● AI Service (ngrok) — recitation eval │ │ Payments, │ │ ● Stripe — payments & payouts │ │ Recitations, │ │ ● Google OAuth2 — social login │ │ Reviews, │ │ ● Gmail SMTP — email / OTP │ │ Availability, │ │ │ │ Streaks │ └──────────────────────────────────────────┘ └─────────────────────┘ ``` --- ## 2. Technology Stack ### Frontend | Concern | Technology | |---|---| | Framework | React 19 + TypeScript | | Build tool | Vite 7 | | Styling | Tailwind CSS + Radix UI (shadcn components) | | Routing | React Router v7 | | Internationalization | i18next + react-i18next (Arabic / English) | | Real-time | STOMP/SockJS (@stomp/stompjs + sockjs-client) | | Video calls | WebRTC (RTCPeerConnection, browser native) | | Payments | @stripe/react-stripe-js + @stripe/stripe-js | | Notifications | Sonner + react-hot-toast | | Icons | Lucide React | | Date picker | react-day-picker | ### Backend | Concern | Technology | |---|---| | Framework | Spring Boot 4.0 (Java 21) | | Security | Spring Security + JWT (jjwt 0.12.5) + OAuth2 Client | | Database ORM | Spring Data JPA + Hibernate | | Database | PostgreSQL | | WebSocket | Spring WebSocket + STOMP (in-memory broker) | | Email | Spring Mail (Gmail SMTP, STARTTLS) | | Payments | Stripe Java SDK 25.3 | | Audio processing | JAVE2 3.5 (ffmpeg wrapper for Linux) | | Google Auth | Google API Client 2.6 | | Monitoring | Spring Boot Actuator | | Boilerplate | Lombok | --- ## 3. User Roles & Capabilities ### Student - Register/login (email or Google) - Complete profile (Quran level, language preference, accent) - Browse and search Sheikhs by rating, price, availability - Book 1-hour sessions with a Sheikh - Pay for sessions via Stripe (credit card) - Join live video sessions (WebRTC) - Record solo recitations and get AI-powered evaluation - View evaluation reports (word accuracy, tashkeel, mistakes) - Track daily streak and practice statistics - Write reviews after completed sessions ### Sheikh - Register/login (email or Google) - Complete profile (bio, specialization, years of experience, hourly rate) - Submit to admin approval process (including interview scheduling) - Set weekly availability (recurring time slots per day) - Receive and manage session requests (accept / reject) - Conduct live video sessions - Write session notes for students - Receive payouts via Stripe Connect after session completion - View their students and session history ### Admin - Approve or reject sheikh applications - Schedule interview sessions with pending sheikhs - Conduct video interview sessions - Manage all users (activate / deactivate accounts) - View full user profiles and session history - Access admin dashboard with platform statistics --- ## 4. Frontend Architecture ### Application Entry Point The app boots from `main.tsx` → `App.tsx`. The provider stack wraps the entire app: ``` BrowserRouter └── LanguageProvider (i18next context) └── AuthProvider (user state, JWT management) └── StudentProvider (student-specific data) └── Routes ``` ### Route Structure Routes are split into three groups: **Public (no auth required)** ``` / Landing page /signin Email login /register Registration /forgot-password OTP request /verify-otp OTP verification /reset-password New password /auth/google Google OAuth callback /complete-profile Role selection after register /complete-profile/student Student profile wizard /complete-profile/sheikh Sheikh profile wizard /sheikh/interview Interview scheduling (pending sheikh) ``` **Protected — Student** ``` /student/dashboard Dashboard with stats, streak, recent activity /student/practice AI-powered recitation practice /student/find-sheikh Sheikh search/browse /student/sheikh/:id Sheikh profile + booking /student/sessions Session list /student/evaluations/:id AI evaluation reports /waiting-room/:id Pre-session waiting room /session/:sessionId Live video session /session-report/:id Post-session report + review /session-waiting/:id Post-session waiting (before report) ``` **Protected — Sheikh** ``` /sheikh/dashboard Dashboard with stats, pending requests /sheikh/sessions Session management /sheikh/students Student list ``` **Protected — Admin** ``` /admin/dashboard Platform statistics /admin/sheikh-approval Sheikh approval queue /admin/users All users management /admin/user/:userId User detail view /admin/interview/:id Live interview session ``` **Common** ``` /profile Edit own profile ``` ### Route Guard Logic `ProtectedRoute` checks three conditions in order: 1. Loading state → show spinner 2. No user → redirect to `/signin` 3. Profile not completed → redirect to `/complete-profile` (except admin, who skips this) ### State Management No Redux. State lives in three React contexts: | Context | Responsibility | |---|---| | `AuthContext` | User identity, JWT token, login/logout/register, session restore on load | | `LanguageContext` | Current language (AR/EN), direction (RTL/LTR) | | `StudentContext` | Student-specific data (streak, stats, etc.) | Auth token is stored in `localStorage` under key `authToken`. User data is stored under `currentUser`. On app load, `AuthContext` re-validates the token by calling `GET /api/auth/verify-profile`. ### Frontend Module Map ``` src/ ├── App.tsx Route definitions + providers ├── main.tsx App entry + Google OAuth provider ├── components/ │ ├── auth/ Login, Register, OTP, Profile wizards │ ├── dashboards/ StudentDashboard, SheikhDashboard, AdminDashboard │ ├── sessions/ LiveSession, WaitingRoom, MySessions, SessionReport │ ├── sheikh/ FindSheikh, SheikhProfile, MyStudents, UpdatePricing │ ├── practice/ RecordingPractice, EvaluationReports │ ├── profile/ ProfilePage │ ├── admin/ SheikhApproval, UserManagement, InterviewSession │ ├── ui/ Reusable shadcn/Radix UI components (50+ components) │ ├── Navbar.tsx Top navigation bar │ └── RoleSwitcher.tsx Dev tool for switching roles ├── hooks/ │ └── useWebRTC.ts WebRTC + STOMP signaling hook ├── lib/ │ ├── authContext.tsx Auth state and API calls │ ├── languageContext.tsx i18n context │ ├── StudentContext.tsx Student data context │ └── api/ All REST API modules (one file per domain) │ ├── aiApi.ts AI analysis service calls │ ├── bookingApi.ts Availability, booking, sessions │ ├── paymentApi.ts Stripe payment flow │ ├── studentApi.ts Student data │ ├── sheikhApi.ts Sheikh data │ ├── adminApi.ts Admin operations │ └── ... ├── types/ │ ├── index.ts Domain types (User, Session, Payment, etc.) │ └── api.ts API helper types (PaginatedResponse, AppError, etc.) └── styles/ └── globals.css ``` --- ## 5. Backend Architecture ### Package Structure The backend is organized by domain slice, not by layer. Each domain has its own controller, service, repository, DTO, and mapper. ``` com.yourcompany.quranapp.quran_app_backend/ ├── config/ │ ├── SecurityConfig.java CORS, JWT filter chain, role rules │ └── GlobalExceptionHandler.java Centralized error responses ├── security/ │ ├── JwtAuthenticationFilter.java Validates Bearer token on every request │ ├── CustomUserDetails.java Wraps User entity, exposes userId + role │ └── UserDetailsServiceImpl.java Loads user by email from DB ├── entity/ JPA entities (data model) ├── dashboard/ │ ├── common/ Auth (login, register, OTP, Google OAuth) │ ├── student/ Student APIs (sessions, recitations, profile) │ ├── sheikh/ Sheikh APIs (sessions, availability, notes) │ └── admin/ Admin APIs (approvals, user management) ├── payment/ Stripe payment controller + service ├── websocket/ WebSocket config + signaling controller ├── google/ Google token verification └── setup/ └── DataLoader.java Seed data on startup ``` ### Security Configuration Spring Security is configured as stateless (no HTTP session). Every request goes through: ``` HTTP Request → JwtAuthenticationFilter → Extract Bearer token from Authorization header → Validate token via JwtService → Load user from DB → Set SecurityContext → SecurityFilterChain → Check role-based access rules → Controller ``` **Endpoint access rules:** | Path pattern | Access | |---|---| | `/api/auth/**` | Public | | `/api/public/**` | Public | | `/ws/**` | Public (WebSocket needs its own auth) | | `/api/admin/**` | ADMIN only | | `/api/sheikhs/**` | STUDENT only (browse sheikhs) | | `/api/sheikh/**` | SHEIKH, ADMIN, STUDENT | | `/api/student/**` | STUDENT, SHEIKH, ADMIN | | `/api/reviews/**` | STUDENT, SHEIKH, ADMIN | | `/api/payments/**` | STUDENT, SHEIKH, ADMIN | | Everything else | Any authenticated user | ### Domain Controllers #### Common — `/api/auth/**` | Endpoint | Description | |---|---| | `POST /register/student` | Register new student | | `POST /register/sheikh` | Register new sheikh | | `POST /login` | Email/password login → returns JWT | | `POST /google` | Google ID token login/register | | `POST /forgot-password` | Send OTP email | | `POST /verify-otp` | Validate OTP | | `POST /reset-password` | Set new password with OTP | | `POST /logout` | Invalidate session (stateless — client discards token) | | `GET /verify-profile` | Check profile completion status | | `POST /complete-google-profile` | Complete profile for Google-registered student | | `POST /complete-google-profile/sheikh` | Complete profile for Google-registered sheikh | #### Student — `/api/student/**` | Endpoint | Description | |---|---| | `GET /sessions` | Get all student sessions (filterable by status) | | `GET /sessions/{id}` | Get single session | | `POST /sessions` | Book a session with a sheikh | | `POST /sessions/{id}/join` | Record student join timestamp | | `POST /sessions/{id}/leave` | Record student leave timestamp | | `GET /recitations` | Get all recitations | | `GET /recitations/recent` | Get top 5 recent recitations | | `GET /recitations/stats` | Get recitation statistics | | `POST /recitations` | Upload audio + surah number → AI eval + save | | `GET /recitations/{id}` | Get single recitation detail | | `GET /profile/complete` | (also via auth) Complete student profile | #### Sheikh — `/api/sheikh/**` | Endpoint | Description | |---|---| | `GET /sessions` | Get sheikh sessions (filterable by status) | | `GET /sessions/{id}` | Get single session | | `POST /sessions/{id}/accept` | Accept session → status = TOPAY | | `POST /sessions/{id}/reject` | Reject/cancel session | | `POST /sessions/{id}/start` | Start session → status = ON_GOING | | `POST /sessions/{id}/end` | End session + save notes → status = COMPLETED | | `POST /sessions/{id}/join` | Record sheikh join timestamp | | `POST /sessions/{id}/leave` | Record sheikh leave timestamp | | `POST /sessions/{id}/notes` | Update session notes | | `GET /availability` | Get sheikh's weekly availability slots | | `PUT /availability` | Set sheikh's weekly availability slots | | `GET /students` | Get sheikh's student list | | `GET /dashboard` | Dashboard statistics | #### Student browsing Sheikhs — `/api/sheikhs/**` | Endpoint | Description | |---|---| | `GET /` | Browse sheikhs (filters: country, price, rating, language) | | `GET /{id}` | Get sheikh public profile | | `GET /{id}/available-dates` | Get available dates in next N days | | `GET /{id}/available-slots/day` | Get time slots for a specific date | | `GET /{id}/available-slots` | Get all slots in a date range | | `GET /{id}/Top-Reviews` | Get top reviews for a sheikh | #### Admin — `/api/admin/**` | Endpoint | Description | |---|---| | `GET /dashboard` | Platform-wide statistics | | `GET /sheikh-approvals` | Pending sheikh applications | | `POST /sheikh-approvals/{id}/approve` | Approve sheikh | | `POST /sheikh-approvals/{id}/reject` | Reject sheikh with reason | | `POST /sheikh-approvals/{id}/schedule-interview` | Schedule interview session | | `GET /users` | All users with filters | | `GET /users/{id}` | Full user profile | | `POST /users/{id}/deactivate` | Deactivate user account | #### Reviews — `/api/reviews/**` | Endpoint | Description | |---|---| | `POST /session/{id}` | Student submits review after session | | `GET /student/session/{id}/details` | Student view of session + review | | `GET /sheikh/session/{id}/details` | Sheikh view of session + review | #### Payments — `/api/payments/**` | Endpoint | Description | |---|---| | `POST /initiate/card` | Create Stripe PaymentIntent, return clientSecret | | `POST /confirm-payment/{intentId}` | Confirm payment with backend after Stripe.js success | | `POST /confirm-session/{sessionId}` | Verify payment is PAID when sheikh accepts | | `POST /payout/{sessionId}` | Transfer funds to sheikh via Stripe Connect | | `POST /refund/{sessionId}` | Refund student via Stripe Refunds API | | `POST /connect/sheikh/{sheikhId}` | Create Stripe Connect account for sheikh | #### WebSocket — `/ws` | Destination | Description | |---|---| | `/app/signal` | Send WebRTC/chat signaling message | | `/topic/session/{id}` | Subscribe to receive signals for a session | --- ## 6. Data Model ### Entity Relationship Summary ``` User (1) ─────────── (0..1) StudentProfile User (1) ─────────── (0..1) SheikhProfile User (1) ─────────── (0..1) Streak User (1) ─────────── (n) Availability [sheikh side] User (1) ─────────── (n) Session [as student] User (1) ─────────── (n) Session [as sheikh] User (1) ─────────── (n) Recitation [as student] User (1) ─────────── (n) Payment [as student] User (1) ─────────── (n) Payment [as sheikh] User (1) ─────────── (n) Review [as student] Session (1) ─────── (0..1) Payment Session (1) ─────── (0..1) Review ``` ### Entity Details #### User ``` id BIGINT PK email VARCHAR UNIQUE passwordHash VARCHAR fullName VARCHAR phone VARCHAR gender VARCHAR country VARCHAR birthDateTime TIMESTAMP role ENUM(STUDENT, SHEIKH, ADMIN) status ENUM(ALIVE, DEAD) [soft delete] isActive BOOLEAN authProvider VARCHAR [LOCAL or GOOGLE] resetPasswordOtp VARCHAR resetPasswordOtpExpiresAt TIMESTAMP createdAt TIMESTAMP Indexes: role, phone, country, is_active, created_at ``` #### StudentProfile ``` id (= user_id) BIGINT PK FK → users.id quranLevel VARCHAR [beginner / intermediate / advanced] preferredLanguage VARCHAR preferredAccent VARCHAR profilePhotoUrl VARCHAR ``` #### SheikhProfile ``` id (= user_id) BIGINT PK FK → users.id bio TEXT specialization VARCHAR yearsOfExperience INT hourlyRate DECIMAL(10,2) ratingAvg DOUBLE ratingCount INT certificateUrl VARCHAR profilePhotoUrl VARCHAR status ENUM(PENDING, APPROVED, REJECTED, INTERVIEW_SCHEDULED) rejectionReason VARCHAR stripeAccountId VARCHAR [Stripe Connect account ID] interviewDateTime TIMESTAMP messageToPendingSheikh VARCHAR Indexes: status, rating_avg, hourly_rate, status+rating_avg ``` #### Session ``` id BIGINT PK scheduledStart TIMESTAMP scheduledEnd TIMESTAMP actualStart TIMESTAMP actualEnd TIMESTAMP studentJoinedAt TIMESTAMP sheikhJoinedAt TIMESTAMP studentLeftAt TIMESTAMP sheikhLeftAt TIMESTAMP status ENUM(REQUESTED, TOPAY, ON_GOING, COMPLETED, CANCELLED, MISSED) price DECIMAL(10,2) sessionDescription VARCHAR(255) sessionNotes TEXT student_id FK → users.id sheikh_id FK → users.id createdAt TIMESTAMP updatedAt TIMESTAMP Indexes: student_id, sheikh_id, status, scheduled_start, sheikh+status+start, student+status, sheikh+student ``` #### Payment ``` id BIGINT PK amount DECIMAL(10,2) currency VARCHAR(10) [Always "EGP" currently, Stripe handles conversion] paymentMethod ENUM(CREDIT_CARD, VODAFONE_CASH) paymentStatus ENUM(PENDING, PAID, REFUNDED, FAILED) transactionId VARCHAR UNIQUE [Stripe PaymentIntent ID] orderId VARCHAR UNIQUE [Stripe Order ID] transferId VARCHAR UNIQUE [Stripe Transfer/Refund ID] platformCommission DECIMAL(10,2) [15% of amount] sheikhPayout DECIMAL(10,2) [85% of amount] student_id FK → users.id sheikh_id FK → users.id session_id FK → sessions.id UNIQUE paymentDate TIMESTAMP capturedAt TIMESTAMP transferredAt TIMESTAMP refundedAt TIMESTAMP Indexes: student_id, sheikh_id, payment_status ``` #### Recitation ``` id BIGINT PK surahName VARCHAR(25) ayahStart INT ayahEnd INT wordScore DECIMAL(4,2) tashkeelScore DECIMAL(4,2) totalScore DECIMAL(4,2) [tajweed score from AI] durationInMinutes DECIMAL(5,2) mistakesReportJson JSONB [full AI response stored as JSON] student_id FK → users.id createdAt TIMESTAMP Index: student_id ``` #### Review ``` id BIGINT PK comment TEXT rate INT (1-5) session_id FK → sessions.id UNIQUE student_id FK → users.id createdAt TIMESTAMP Index: student_id ``` #### Availability ``` id BIGINT PK dayOfWeek ENUM(MONDAY..SUNDAY) startTime TIME endTime TIME sheikh_id FK → users.id Indexes: sheikh_id, day_of_week ``` #### Streak ``` id (= user_id) BIGINT PK FK → users.id currentStreak INT longestStreak INT lastActivityDate DATE ``` --- ## 7. Authentication & Authorization Flow ### Email/Password Registration ``` Frontend Backend ───────── ─────── POST /api/auth/register/student { fullName, email, password, phone, gender, country, birthDate } ───► Validate fields Hash password (BCrypt) Save User (role=STUDENT) Generate JWT ◄─── { token, user } POST /api/auth/verify-profile Authorization: Bearer ───► Decode token → userId Check StudentProfile exists ◄─── { profileCompleted, profileStatus } → if profileCompleted = false → redirect to /complete-profile/student POST /api/student/profile/complete { quranLevel, language, accent } ───► Save StudentProfile ◄─── 200 OK → redirect to /student/dashboard ``` ### Google OAuth Flow ``` Frontend Backend Google ───────── ─────── ────── User clicks "Sign in with Google" → Google Identity Services returns idToken ◄──────► Verify idToken POST /api/auth/google { idToken, role: "STUDENT" } ───► Verify idToken with Google API Client Find/create User Generate JWT ◄─── { token, user } (same profile completion flow as above) ``` ### JWT Token Lifecycle - Token expiry: 24 hours (86400000 ms, configurable via `app.jwt.expiration-ms`) - Token storage: `localStorage` key `authToken` - Token format: Standard JWT signed with HMAC-SHA key (`app.jwt.secret-key`) - Every protected API request sends: `Authorization: Bearer ` - `JwtAuthenticationFilter` validates token on every request before it reaches the controller - On app load, `AuthContext` restores session by re-calling `GET /api/auth/verify-profile` ### Password Reset (OTP flow) ``` POST /api/auth/forgot-password { email } → Generate 6-digit OTP → Store OTP + expiry in users table → Send OTP via Gmail SMTP POST /api/auth/verify-otp { email, otp } → Validate OTP matches + not expired → Return success POST /api/auth/reset-password { email, otp, newPassword } → Re-validate OTP → BCrypt hash new password → Clear OTP fields ``` --- ## 8. Session Lifecycle ### Complete Flow: Booking to Completion ``` 1. REQUESTED Student books a session → POST /api/student/sessions Session created with status = REQUESTED 2. TOPAY (payment pending) Sheikh accepts → POST /api/sheikh/sessions/{id}/accept Status changes to TOPAY 3. Payment Student sees payment prompt Frontend creates PaymentIntent → POST /api/payments/initiate/card Backend returns { clientSecret, publishableKey } Stripe.js handles card entry on frontend On Stripe success → POST /api/payments/confirm-payment/{intentId} Status changes to SCHEDULED (payment confirmed) 4. ON_GOING At session time, both parties join waiting room Sheikh starts → POST /api/sheikh/sessions/{id}/start Status = ON_GOING Both parties join live WebRTC session 5. COMPLETED Sheikh ends session + adds notes → POST /api/sheikh/sessions/{id}/end Status = COMPLETED Payout triggered → POST /api/payments/payout/{sessionId} Platform keeps 15%, sheikh receives 85% via Stripe Connect 6. Review Student writes review → POST /api/reviews/session/{id} Sheikh rating updated (rolling average) Exception paths: → Sheikh rejects → CANCELLED → Session time passes without start → MISSED → student refunded ``` ### Session Statuses ``` REQUESTED → TOPAY → (SCHEDULED) → ON_GOING → COMPLETED └──► CANCELLED (sheikh rejects) └──────────────► MISSED (no-show, auto-refund) ``` --- ## 9. Real-Time Communication (WebRTC + WebSocket) ### Architecture The backend acts as a **signaling server** only. It does not relay audio/video. Actual media flows peer-to-peer between browser clients via WebRTC. ``` Student Browser Backend (Signaling) Sheikh Browser ─────────────── ───────────────── ────────────── Connect SockJS /ws ──────► STOMP broker Connect SockJS /ws Subscribe /topic/ /topic/session/{id} Subscribe /topic/ session/{id} session/{id} Send join signal ──────► Broadcast to topic ────────► Receives join /topic/session/{id} Creates RTCPeerConnection Creates RTCPeerConnection Creates Offer ──────► Broadcast ────────► Receives Offer Creates Answer Receives Answer ◄────── Broadcast ◄──────── Sends Answer ICE exchange ◄──────► Broadcast ◄────────► ICE exchange Direct P2P stream ════════════════════════════════════════► Direct P2P stream (audio + video, no server relay) ``` ### Signaling Message Types | Type | Direction | Purpose | |---|---|---| | `join` | Any → Topic | Notify that a user entered the session room | | `offer` | Caller → Topic | WebRTC SDP offer | | `answer` | Callee → Topic | WebRTC SDP answer | | `ice-candidate` | Any → Topic | ICE connectivity candidates | | `chat` | Any → Topic | In-session text chat message | ### ICE Servers (STUN) The frontend uses Google's public STUN servers for NAT traversal: - `stun:stun.l.google.com:19302` - `stun:stun1.l.google.com:19302` - `stun:stun2.l.google.com:19302` No TURN server is currently configured. This means sessions behind symmetric NAT may fail to connect. ### WebSocket Config (Backend) - Endpoint: `/ws` with SockJS fallback - Application destination prefix: `/app` (send to controller) - Broker topics: `/topic` (broadcast), `/queue` (point-to-point) - User destination prefix: `/user` --- ## 10. Payment Flow (Stripe) ### Architecture The system uses **Stripe Payment Intents** for collecting student payments and **Stripe Connect** (Express accounts) for paying out sheikhs. ### Student Payment Flow ``` Frontend Backend Stripe ───────── ─────── ────── POST /api/payments/initiate/card { sessionId, studentId, sheikhId, amount } ───► Create PaymentIntent (amount in smallest unit) ◄──── { clientSecret, publishableKey, paymentIntentId } Stripe.js loads card form User enters card details stripe.confirmCardPayment(clientSecret) ──────► Charge card ◄────── Payment confirmed POST /api/payments/confirm-payment/{intentId} ───► Retrieve PaymentIntent from Stripe Verify status = succeeded Save Payment record (PAID) Update Session status → SCHEDULED ◄──── { success: true } ``` ### Sheikh Payout Flow (after session completion) ``` Backend Stripe ─────── ────── POST /api/payments/payout/{sessionId} Verify session is COMPLETED Calculate: sheikhPayout = amount × 0.85 commission = amount × 0.15 Create Stripe Transfer to sheikh's connected account (stripeAccountId) ──────► Transfer funds Update Payment: transferredAt, transferId ◄────── Transfer confirmed ``` ### Refund Flow (missed/cancelled sessions) ``` Backend Stripe ─────── ────── POST /api/payments/refund/{sessionId} Verify payment is PAID Create Stripe Refund for full amount ──────► Refund issued Update Payment: refundedAt, paymentStatus=REFUNDED ``` ### Platform Commission Commission percentage is configurable: `platform.commission.percentage = 15` --- ## 11. AI Recitation Evaluation Flow ### Flow ``` Frontend Backend AI Service ───────── ─────── ────────── User records audio in browser (MediaRecorder API) POST /api/student/recitations multipart/form-data { surahNumber, audio file } ───► Receive audio file Convert to WAV (JAVE2) Forward to AI service: POST {AI_BASE_URL}/analyze { audio, surah_number } ──────► Speech recognition Text alignment with Quran reference Error detection: - Extra words - Missed words - Tashkeel errors ◄────── { surah_info, overall_stats, ayahs[] } Parse AI response Calculate scores: - wordScore - tashkeelScore - totalScore Store Recitation (mistakesReportJson as JSONB) ◄──── RecitationDetailsDto Display scores + mistake report ``` ### AI Response Structure The AI service returns per-ayah analysis: - `words_stage`: extra words and missed words per ayah - `tashkeel_stage`: per-word tashkeel comparison with reasons - `overall_stats`: word accuracy % and tashkeel accuracy % Scores are stored as JSONB in `mistakesReportJson` for full replay capability on the frontend. ### Direct AI Call (frontend also calls AI directly) The frontend `aiApi.ts` can also call the AI service directly (bypassing the backend) for immediate feedback before saving. This is used in the `RecordingPractice` component. --- ## 12. Sheikh Approval Flow ### Steps ``` 1. Register Sheikh registers → role = SHEIKH Status = PENDING (no SheikhProfile yet) 2. Complete Profile POST /api/sheikh/profile/complete { bio, specialization, yearsOfExperience, hourlyRate, certificate } SheikhProfile created → status = PENDING 3. Admin Review Admin sees sheikh in approval queue GET /api/admin/sheikh-approvals 4. Interview Scheduling Admin schedules interview: POST /api/admin/sheikh-approvals/{id}/schedule-interview { dateTime, message } Status → INTERVIEW_SCHEDULED Sheikh notified (email or in-app) 5. Interview Session Admin and sheikh join live video call (same WebRTC infrastructure as student sessions) Route: /admin/interview/:sheikhId Route: /sheikh/interview-session/:sheikhId 6. Decision Approve: status → APPROVED, sheikh can accept students Reject: status → REJECTED, rejectionReason saved 7. Post-Approval Sheikh sets hourly rate: PUT /api/sheikh/update-pricing Sheikh sets availability: PUT /api/sheikh/availability Sheikh appears in student search: GET /api/sheikhs/ ``` --- ## 13. Internationalization (i18n) The frontend supports Arabic and English using i18next. - Language detection: browser preference (`i18next-browser-languagedetector`) - Translations: `src/lib/translations.ts` - Language context: `src/lib/languageContext.tsx` - Direction: Automatically switches between LTR (English) and RTL (Arabic) - Language switcher: `src/components/LanguageSwitcher.tsx` --- ## 14. Database Optimization ### JDBC Batching (Hibernate) Enabled globally for bulk insert/update performance: ```yaml hibernate: jdbc: batch_size: 20 order_inserts: true order_updates: true ``` ### Indexes Key composite and single-column indexes defined at entity level: | Table | Indexes | |---|---| | users | role, phone, country, is_active, created_at | | sessions | student_id, sheikh_id, status, scheduled_start, sheikh+status+start, student+status, sheikh+student | | sheikh_profile | status, rating_avg, hourly_rate, status+rating_avg | | payment | student_id, sheikh_id, payment_status | | recitations | student_id | | review | student_id | | availability | sheikh_id, day_of_week | --- ## 15. Configuration & Environment ### Backend Environment Variables | Variable | Purpose | |---|---| | `DB_URL` | PostgreSQL JDBC URL | | `PGUSER` | Database username | | `PGPASSWORD` | Database password | | `JWT_SECRET` | JWT HMAC signing key | | `MAIL_USERNAME` | Gmail address for OTP emails | | `MAIL_PASSWORD` | Gmail app password | | `STRIPE_SECRET_KEY` | Stripe secret key | | `STRIPE_PUBLISHABLE_KEY` | Stripe publishable key | | `GOOGLE_CLIENT_ID` | Google OAuth client ID | ### Frontend Environment Variables | Variable | Purpose | |---|---| | `VITE_API_BASE_URL` | Backend base URL (defaults to `http://localhost:8080`) | ### AI Service External Python service exposed via ngrok: - Base URL: configured in `app.ai.base-url` - Connect timeout: 5s - Read timeout: 600s (10 min, for long audio files) - Endpoint: `POST /analyze` with `{ audio, surah_number }` --- ## 16. Cross-Cutting Concerns ### Error Handling - Backend: `GlobalExceptionHandler` catches all exceptions and returns consistent JSON error responses - Frontend: `AppError` class standardizes error handling across all API modules with `status` + `path` context - Frontend: `ErrorBoundary` component wraps dashboards to prevent full-page crashes ### CORS Backend allows requests from `http://localhost:*` with credentials. For production this needs to be restricted to the actual frontend domain. ### Monitoring Spring Boot Actuator is enabled with all endpoints exposed (`management.endpoints.web.exposure.include: "*"`). For production this should be restricted to health + metrics only. ### Logging ```yaml org.hibernate.SQL: DEBUG # All SQL statements org.hibernate.stat: DEBUG # Query counts per session ``` Hibernate statistics are enabled (`generate_statistics: true`) for N+1 query detection during development. ### Schema Management JPA `ddl-auto: update` — Hibernate auto-updates the schema on startup. This is acceptable for development but should be replaced with Flyway or Liquibase for production. ### Audio Processing JAVE2 wraps ffmpeg. The Linux 64-bit native binary is included as a Maven dependency (`jave-nativebin-linux64`). Audio files uploaded by students are converted to WAV before being sent to the AI service.