281 lines
9.7 KiB
Markdown
281 lines
9.7 KiB
Markdown
# 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 1–3 + 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 1–3, 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 3–5 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.
|