Files
tm_app/docs/home-page-api-calls.md
T
AmirReza Jamali 72881df210
continuous-integration/drone/push Build encountered an error
Implement tender workflows and dashboard integration
2026-07-14 11:12:09 +03:30

281 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Home Page API Calls
Document for the backend team. Describes every HTTP request the mobile/web client fires when the user opens `/home`, including call order, parameters, and what blocks the UI from rendering.
**Context:** Home page load feels slow. The client currently waits on **3 sequential API calls** before showing content. A 4th call runs in the background; a 5th may fire when the user has no "your tenders".
**Base URL:** `https://app.opplenz.com`
**Auth:** All requests include `Authorization: Bearer <access_token>`.
---
## Summary
| # | Endpoint | Method | Blocking UI? | When |
|---|----------|--------|--------------|------|
| 1 | `/api/v1/tender-approvals/stats` | GET | **Yes** | Always on home load |
| 2 | `/api/v1/tender-approvals?limit=10&offset=0` | GET | **Yes** | Always on home load |
| 3 | `/api/v1/feedback?feedback_type=like&limit=1&offset=0` | GET | **Yes** | Always on home load |
| 4 | `/api/v1/notifications?seen=false&limit=1&event_type=PUSH` | GET | No (background) | App shell mount (parallel) |
| 5 | `/api/v1/tenders/recommend?limit=10&offset=0` | GET | No | Only when call #2 returns an empty tender list |
**Minimum calls on first load:** 4 (calls 13 + 4)
**Maximum calls on first load:** 5 (adds call 5 when user has no your-tenders)
**Perceived load time (client-side):** Sum of response times for calls 1 → 2 → 3 (sequential `await` chain).
---
## Call sequence
```
User opens /home
├─ [parallel, background] App shell
│ └─ GET /api/v1/notifications?seen=false&limit=1&event_type=PUSH
└─ [blocking] HomeViewModel.init()
├─ 1. GET /api/v1/tender-approvals/stats ← await
├─ 2. GET /api/v1/tender-approvals?limit=10&offset=0 ← await
│ └─ if tenders[] is empty (fire-and-forget, not awaited):
│ GET /api/v1/tenders/recommend?limit=10&offset=0
└─ 3. GET /api/v1/feedback?feedback_type=like&limit=1&offset=0 ← await
└─ UI renders (_isLoading = false)
```
Source: `lib/view_models/home_view_model.dart``init()`
---
## API details
### 1. Tender approval stats
Partnership / self-apply progress rings on the home dashboard.
| Field | Value |
|-------|-------|
| **Method** | `GET` |
| **Path** | `/api/v1/tender-approvals/stats` |
| **Query params** | None |
| **Blocking** | Yes — first call in `init()` |
| **Response fields used** | `data.partnership_count`, `data.self_apply_count` |
**Example:**
```http
GET /api/v1/tender-approvals/stats
Authorization: Bearer <token>
Accept: application/json
```
---
### 2. Your tenders (horizontal list)
Main tender carousel and "Your Tenders" section.
| Field | Value |
|-------|-------|
| **Method** | `GET` |
| **Path** | `/api/v1/tender-approvals` |
| **Query params** | `limit=10`, `offset=0` |
| **Optional filters** | Not sent from home (`status`, `created_from`, `created_to` are empty) |
| **Blocking** | Yes — second call in `init()` |
| **Response fields used** | `data.tenders[].tender` (full tender objects), `data.metadata.pages` (pagination) |
**Example:**
```http
GET /api/v1/tender-approvals?limit=10&offset=0
Authorization: Bearer <token>
Accept: application/json
```
**Note:** This is likely the heaviest call — it returns up to 10 full tender objects with nested data.
---
### 3. Liked tenders count
"Liked Tenders" stat card count.
| Field | Value |
|-------|-------|
| **Method** | `GET` |
| **Path** | `/api/v1/feedback` |
| **Query params** | `feedback_type=like`, `limit=1`, `offset=0` |
| **Blocking** | Yes — third call in `init()` |
| **Response fields used** | `data.meta.total` only (client ignores the single returned item) |
**Example:**
```http
GET /api/v1/feedback?feedback_type=like&limit=1&offset=0
Authorization: Bearer <token>
Accept: application/json
```
**Optimization opportunity:** Client only needs a count. A dedicated count endpoint (or `meta.total` without loading feedback records) would be faster than a full feedback query.
---
### 4. Unread notification check (background)
Red dot on the notification tab icon.
| Field | Value |
|-------|-------|
| **Method** | `GET` |
| **Path** | `/api/v1/notifications` |
| **Query params** | `seen=false`, `limit=1`, `event_type=PUSH` |
| **Blocking** | No — fired from app shell `postFrameCallback` |
| **Response fields used** | Presence of any result (updates `NotificationStateService`) |
**Example:**
```http
GET /api/v1/notifications?seen=false&limit=1&event_type=PUSH
Authorization: Bearer <token>
Accept: application/json
```
Source: `lib/core/routes/app_shell/app_shell_screen.dart`
---
### 5. Recommended tenders (conditional fallback)
Shown when the user has no "your tenders". Replaces the horizontal list label with "Recommended Tenders".
| Field | Value |
|-------|-------|
| **Method** | `GET` |
| **Path** | `/api/v1/tenders/recommend` |
| **Query params** | `limit=10`, `offset=0` |
| **Blocking** | No — triggered inside `getYourTenders()` without `await` |
| **Condition** | Fires only when call #2 returns `data.tenders` empty |
| **Response fields used** | `data.tenders[]` (full tender objects) |
**Example:**
```http
GET /api/v1/tenders/recommend?limit=10&offset=0
Authorization: Bearer <token>
Accept: application/json
```
---
## Pagination (scroll, not initial load)
After the home page renders, scrolling the horizontal tender list near the end triggers additional calls:
| Mode | Endpoint | Params |
|------|----------|--------|
| Your tenders | `GET /api/v1/tender-approvals` | `limit=10`, `offset=<page * 10>` |
| Recommended (fallback mode) | `GET /api/v1/tenders/recommend` | `limit=10`, `offset=<page * 10>` |
Page size is always **10**.
---
## Re-fetch triggers
The full `init()` sequence (calls 13, and possibly 5) runs again when:
- User switches away from Home tab and returns to it
- User navigates to Liked Tenders and comes back (`homeViewModel.init()` on return)
This means the same 35 API calls can repeat on every tab switch back to Home.
---
## APIs defined but NOT called on home load
| Endpoint | Notes |
|----------|-------|
| `GET /api/v1/feedback/stats/company` | Defined in client (`HomeApi.statsCompany`) but never called from home |
Some home stat cards show hardcoded `0` values (approved tenders count, tender value) — no API backing today.
---
## Performance notes for backend
### Client-side bottleneck
The client **does not parallelize** calls 1, 2, and 3. Total time-to-interactive ≈ `T(stats) + T(your-tenders) + T(liked-count)`.
### Suggested backend investigations
1. **`GET /api/v1/tender-approvals?limit=10&offset=0`** — Likely the slowest call. Check query plans, N+1 joins, and payload size (10 full tender objects).
2. **`GET /api/v1/tender-approvals/stats`** — Runs before every home load; consider caching per company/user.
3. **`GET /api/v1/feedback?feedback_type=like&limit=1&offset=0`** — Client only reads `meta.total`. A lightweight `GET /api/v1/feedback/count?feedback_type=like` would avoid loading feedback rows.
4. **`GET /api/v1/tenders/recommend`** — May involve recommendation/AI logic; profile separately from tender-approvals.
### Suggested backend optimizations
| Idea | Benefit |
|------|---------|
| Single aggregated `GET /api/v1/home` endpoint | One round-trip instead of 3 sequential |
| Return stats + liked count alongside tender-approvals list | Eliminate calls 1 and 3 |
| Slim tender DTO for list views (id, title, deadline, status only) | Smaller payloads on call #2 and #5 |
| Cache stats and liked count with short TTL | Faster repeat visits / tab switches |
### Suggested client optimizations (for reference)
These are frontend changes the mobile team could make independently:
- Run calls 1, 2, and 3 in parallel (`Future.wait`)
- Do not re-run full `init()` on every tab switch — refresh only stale data
- Await the recommend fallback before clearing loading state (avoids UI flicker)
---
## Source files (client)
| File | Role |
|------|------|
| `lib/view_models/home_view_model.dart` | Orchestrates all home API calls |
| `lib/data/repositories/home_repository.dart` | Repository layer |
| `lib/data/services/home_service.dart` | Stats, recommend, notifications |
| `lib/data/services/your_tenders_service.dart` | Your tenders list |
| `lib/data/services/liked_tenders_service.dart` | Liked count |
| `lib/data/services/api/home_api.dart` | Endpoint path constants |
| `lib/data/services/api/your_tenders_api.dart` | Tender-approvals URL builder |
| `lib/data/services/api/liked_tenders_api.dart` | Feedback URL builder |
| `lib/core/routes/app_shell/app_shell_screen.dart` | Background notification check |
| `lib/views/home/pages/home_screen.dart` | Re-init on tab return |
---
## Quick test commands
Replace `$TOKEN` with a valid access token.
```bash
# 1. Stats
curl -s -w "\n%{http_code} %{time_total}s\n" \
'https://app.opplenz.com/api/v1/tender-approvals/stats' \
-H "Authorization: Bearer $TOKEN" | tail -1
# 2. Your tenders
curl -s -w "\n%{http_code} %{time_total}s\n" \
'https://app.opplenz.com/api/v1/tender-approvals?limit=10&offset=0' \
-H "Authorization: Bearer $TOKEN" | tail -1
# 3. Liked count
curl -s -w "\n%{http_code} %{time_total}s\n" \
'https://app.opplenz.com/api/v1/feedback?feedback_type=like&limit=1&offset=0' \
-H "Authorization: Bearer $TOKEN" | tail -1
# 4. Notifications (background)
curl -s -w "\n%{http_code} %{time_total}s\n" \
'https://app.opplenz.com/api/v1/notifications?seen=false&limit=1&event_type=PUSH' \
-H "Authorization: Bearer $TOKEN" | tail -1
# 5. Recommended (only when your tenders empty)
curl -s -w "\n%{http_code} %{time_total}s\n" \
'https://app.opplenz.com/api/v1/tenders/recommend?limit=10&offset=0' \
-H "Authorization: Bearer $TOKEN" | tail -1
```
Run all five and compare `time_total` to identify the slowest endpoint.