# ASAP Poliglota

> **AI-powered English conversation practice app** — real-time voice conversations, contextual simulations, and on-demand AI performance evaluation, all in one mobile experience.

<p align="center">
  <img src="screenshots/01_splash.jpg" width="18%" />
  <img src="screenshots/02_home.jpg" width="18%" />
  <img src="screenshots/03_voice_dashboard.jpg" width="18%" />
  <img src="screenshots/04_scenario_select.jpg" width="18%" />
  <img src="screenshots/05_level_select.jpg" width="18%" />
</p>

<p align="center">
  <img src="screenshots/06_usage_dashboard.jpg" width="18%" />
  <img src="screenshots/07_text_conversation.jpg" width="18%" />
  <img src="screenshots/08_text_chat.jpg" width="18%" />
  <img src="screenshots/09_ai_evaluation.jpg" width="18%" />
  <img src="screenshots/10_my_plan.jpg" width="18%" />
</p>

---

## What is ASAP Poliglota?

ASAP Poliglota is a mobile application designed to help Portuguese speakers practice English conversation through AI-powered simulations. Users can practice **by voice in real time** or **by text at their own pace**, choosing from curated everyday scenarios and adjusting the difficulty level (Basic, Intermediate, Advanced).

When requested by the learner, the AI evaluator analyzes the conversation and delivers structured feedback: a performance score, identified strengths, recurring issues, and an actionable tip for the next practice.

The app is subscription-based, with usage limits enforced per plan (voice minutes, text conversations, AI evaluations), managed through a dedicated billing microservice integrated with Hotmart.

---

## Architecture Overview

The system has **two domain backend microservices**, a **React Native mobile client**, and a dedicated **email delivery service**. Firebase owns authentication, while domain services own customer eligibility, subscriptions, conversations, and usage rules.

```mermaid
graph TD
    Mobile["📱 asap-mobile-app\nReact Native 0.83 · TypeScript\nFirebase Passwordless · WebRTC"]

    Subscription["asap-customer-subscription-app\n:8083/api\n─────────────────\n• Identity provisioning & onboarding\n• Login-link orchestration\n• Plans & subscriptions\n• Hotmart webhooks\n─────────────────\nPostgreSQL"]

    Conversation["asap-language-conversation-app\n:8082/api\n─────────────────\n• Conversations: text + voice\n• Spring AI evaluation engine\n• Usage limits: Redis TTL\n• OpenAI Realtime token proxy\n─────────────────\nPostgreSQL · Redis"]

    Firebase["🔐 Firebase Authentication\nPasswordless Email Link\nID Token issuer · Admin SDK"]
    Notifications["✉️ notifications-app\nSMTP delivery only"]
    OpenAI["🤖 OpenAI\nRealtime API · gpt-4o-mini"]

    Mobile -->|request login link| Subscription
    Subscription -->|generate one-time link| Firebase
    Subscription -->|rendered email via Feign| Notifications
    Notifications -->|SMTP email| Mobile
    Mobile -->|complete email-link sign-in| Firebase
    Mobile -->|Firebase ID Token Bearer| Subscription
    Mobile -->|Firebase ID Token Bearer| Conversation
    Conversation -->|Feign HTTP| Subscription
    Subscription -->|validate issuer, audience & JWK| Firebase
    Conversation -->|validate issuer, audience & JWK| Firebase
    Conversation -->|ephemeral token| OpenAI
    Mobile -->|WebRTC direct| OpenAI
```

> Each domain service owns its own database. Inter-service communication uses **OpenFeign HTTP clients only** — no shared databases. The notification service only delivers pre-rendered email and never owns login eligibility or Firebase Admin credentials.

---

## Passwordless Authentication Flow

```mermaid
sequenceDiagram
    participant App as 📱 Mobile App
    participant Subscription as customer-subscription
    participant Firebase as 🔐 Firebase Auth
    participant Mail as ✉️ notifications-app / SMTP

    App->>Subscription: POST /subscriptions/auth/login-link { email }
    Note over Subscription: Normalize email, check eligibility,<br/>apply anti-abuse and anti-enumeration controls
    Subscription->>Firebase: Generate temporary, one-time sign-in link
    Firebase-->>Subscription: Email sign-in link
    Note over Subscription: Render branded HTML with FreeMarker
    Subscription->>Mail: Send pre-rendered email via authenticated Feign call
    Mail-->>App: Deliver email via SMTP
    Subscription-->>App: Generic successful response
    Note over App,Subscription: The API never returns the sign-in link<br/>or reveals whether the email exists
    App->>App: User taps link and Android App Link opens the app
    App->>Firebase: signInWithEmailLink(email, link)
    Firebase-->>App: Authenticated session + Firebase ID Token
    App->>Subscription: API requests with Bearer ID Token
    App->>Subscription: Refresh once and retry after a 401
```

The mobile app only requests delivery and completes the Firebase sign-in. It never calls the notification service directly. The notification service receives neither Firebase Admin credentials nor responsibility for deciding who may sign in.

During initial customer provisioning, the subscription service also sends an idempotent welcome email containing a newly generated first-access link. Link generation, template rendering, or delivery failures do not roll back an otherwise successful customer provision.

---

## Voice Pipeline — Real-Time Conversation

The voice flow is designed for **minimal latency and zero audio exposure on the backend**:

```mermaid
sequenceDiagram
    participant App as 📱 Mobile App
    participant API as asap-language-conversation-app
    participant OAI as 🤖 OpenAI

    App->>API: POST /conversations/sessions
    API->>OAI: Request ephemeral token
    OAI-->>API: Ephemeral token
    API-->>App: Token + conversationId

    App->>OAI: WebRTC (direct, full-duplex audio)
    Note over App,OAI: Audio never touches the backend.<br/>Real-time, sub-second latency.

    App->>API: POST /conversations/{id}/transcript
    App->>API: PATCH /conversations/{id}/finish
    App->>API: POST /conversations/{id}/audio-review (on demand)
    API->>OAI: Spring AI ChatClient → gpt-4o-mini
    OAI-->>API: Structured evaluation
    API-->>App: Score · Strengths · Issues · Tip
```

This architecture ensures **real-time responsiveness** through direct WebRTC while keeping sensitive audio off the application servers. Only the transcript explicitly submitted after the session is persisted and used for an on-demand review.

---

## AI Evaluation Engine

Built with **Spring AI** (`spring-ai-starter-model-openai`, BOM `1.1.2`), the evaluation engine:

- Receives the conversation text or transcript when the learner requests a review
- Sends it to `gpt-4o-mini` through a structured `ChatClient` prompt
- Returns a scored, structured evaluation containing:
  - **Overall score** (0–100)
  - **Performance summary**
  - **Identified strengths**
  - **Main recurring issue**
  - **Actionable improvement tip** for the next session

Prompts are version-controlled as repeatable SQL seeds (`R__prompts.sql`), allowing prompt iteration without code deployments.

---

## Subscription, Provisioning & Billing

`asap-customer-subscription-app` manages the customer and subscription lifecycle:

- **Firebase identity provisioning** — creates and associates the Firebase user during the first approved purchase
- **Passwordless login orchestration** — validates eligibility, rate-limits requests, generates Firebase links, renders FreeMarker templates, and delegates SMTP delivery
- **Welcome email** — sends the first-access link once per customer after provisioning, without rolling back provisioning if delivery fails
- **Hotmart webhook integration** — handles purchase, cancellation, and plan-change events validated by `hottok`
- **Plan management** — plans defined as repeatable SQL seeds, without hardcoded configuration
- **Feign clients** — expose plan/level data to the conversation service and call the internal notification service

Usage limits (voice minutes, text conversations, AI evaluations) are **enforced in Redis** with TTL-based reset cycles, preventing database hits on every request.

---

## Observability & Privacy

Firebase Crashlytics captures native and fatal JavaScript crashes automatically. Selected handled technical failures — unexpected HTTP failures, authentication/refresh infrastructure failures, and WebRTC failures — are recorded through a centralized sanitized reporter.

Crash reports never include email addresses, Firebase UIDs, ID Tokens, `Authorization` headers, magic links, `oobCode`, request payloads, raw provider responses, SDP, ephemeral keys, audio, messages, or transcripts.

---

## Tech Stack

### `asap-language-conversation-app`

| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.5.8 |
| AI | Spring AI 1.1.2 · OpenAI `gpt-4o-mini` |
| Database | PostgreSQL · Flyway migrations |
| Cache | Redis (session heartbeat · usage timers) |
| Mapping | MapStruct 1.6.3 |
| Auth | Firebase ID Token · OAuth2 Resource Server · Firebase JWK validation |
| HTTP Client | Spring Cloud OpenFeign |
| Testing | JUnit 5 · Mockito · Testcontainers (Postgres + Redis) · JaCoCo |
| Docs | springdoc-openapi (Swagger UI) |

### `asap-customer-subscription-app`

| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.5.8 |
| Database | PostgreSQL · Flyway · Hibernate Types (JSON columns) |
| Auth | Firebase Authentication · Firebase Admin SDK · ID Token validation |
| Email | FreeMarker · OpenFeign · notifications-app/SMTP |
| Billing | Hotmart webhook integration |
| Testing | JUnit 5 · Mockito · Testcontainers (Postgres) · JaCoCo |
| Docs | springdoc-openapi (Swagger UI) |

### `notifications-app`

| Layer | Technology |
|---|---|
| Responsibility | Delivery of pre-rendered email only |
| Transport | SMTP |
| Integration | Internal authenticated HTTP API |
| Security boundary | No login eligibility decisions or Firebase Admin credentials |

### `asap-mobile-app`

| Layer | Technology |
|---|---|
| Framework | React Native 0.83.1 · React 19 |
| Language | TypeScript 5.8.3 |
| Auth | React Native Firebase Auth · Passwordless Email Link · Android App Links |
| API security | Firebase ID Token Bearer · one refresh/retry after 401 |
| Monitoring | Firebase Crashlytics |
| Voice | react-native-webrtc 124.0.7 |
| HTTP | Axios 1.13.2 |
| Navigation | React Navigation 7 |
| UI | react-native-svg · Lottie · lucide-react-native |
| Testing | Jest 29 · @testing-library/react-native |

### Infrastructure

| Component | Technology |
|---|---|
| Containerization | Docker |
| Reverse proxy / API Gateway | Nginx |
| Deployment | VPS |
| CI/CD | Jenkins |
| Identity | Firebase Authentication |
| Email delivery | Dedicated notifications-app instance · SMTP |

---

## Key Engineering Decisions

**1. WebRTC direct-to-OpenAI for voice**

Rather than proxying audio through the backend, the mobile app establishes a direct WebRTC connection to OpenAI after receiving an ephemeral token. This eliminates audio latency, reduces infrastructure costs, and avoids storing audio on application servers.

**2. Separate databases per service**

Each domain microservice owns its PostgreSQL schema. The conversation service never reads the subscription database directly — it calls the subscription service through Feign.

**3. Redis for usage enforcement**

Usage limits are tracked in Redis with TTL-based windows. This avoids expensive database writes on every request and enables fast limit checks.

**4. Prompt versioning as SQL seeds**

AI prompts are stored as repeatable Flyway migrations (`R__prompts.sql`), allowing controlled prompt iteration through database migrations.

**5. Testcontainers for realistic integration tests**

The services use real Postgres and Redis containers in CI, avoiding in-memory substitutes that can hide production-specific behavior.

**6. Backend-generated Firebase passwordless links**

Firebase remains the identity provider, while the subscription service owns eligibility, anti-abuse controls, branding, template rendering, and delivery orchestration. Links are temporary, one-time credentials and are never returned by the public endpoint.

**7. Sanitized mobile observability**

Crashlytics receives only selected technical failures with controlled metadata. Authentication credentials and learning content never become crash-report context.

---

## App Screens

| Splash | Home | Voice Dashboard |
|---|---|---|
| ![](screenshots/01_splash.jpg) | ![](screenshots/02_home.jpg) | ![](screenshots/03_voice_dashboard.jpg) |

| Scenario Selection | Level Selection | Usage & Plan |
|---|---|---|
| ![](screenshots/04_scenario_select.jpg) | ![](screenshots/05_level_select.jpg) | ![](screenshots/10_my_plan.jpg) |

| Text Conversation | AI Evaluation Score | AI Feedback Detail |
|---|---|---|
| ![](screenshots/08_text_chat.jpg) | ![](screenshots/09_ai_evaluation.jpg) | ![](screenshots/09_ai_evaluation.jpg) |

---

## Status

> ✅ **Available on Google Play** — production Android release with Firebase passwordless authentication.

---

## Author

**Wellington Gonçalves Pires** — Senior Java Backend Engineer  
[linkedin.com/in/wellington-goncalves-pires](https://www.linkedin.com/in/wellington-goncalves-pires) · [github.com/wellpires](https://github.com/wellpires)

---

## Live Technical Page

[wellpires.github.io/asap-poliglota](https://wellpires.github.io/asap-poliglota/)
