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

9.7 KiB
Raw Blame History

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.dartinit()


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:

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:

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:

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:

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


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:

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.

# 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.