# Mini App Read-Performance Implementation

## Objective

Keep the landing, project, liked-project, purchase, selected-purchase, and search screens responsive as the catalogue grows. A project with 1,000 or more plots must never cause all plots to be read, transformed, or sent to the mini app in one request.

## Architecture

The existing RPC entry point remains `POST /api/v1/miliki`. New operations are additive, so older clients continue to work while the mini app uses the optimized v2 projections.

| Screen | RPC method | Pagination | Notes |
| --- | --- | --- | --- |
| Landing | `landing_mini_v2` | Fixed top six lists | Shared five-minute base cache; user likes are attached outside the cache |
| Project header | `getproperty_summary` | Not applicable | Does not contain `propertyplots`; cached for 15 minutes |
| Project plots | `getpropertyplots_v2` | Cursor, default 30/max 50 | SQL-level status, land-use, and plot-number filters |
| Liked projects | `user_project_likes_v2` | Cursor, default 20/max 50 | Compact cards only |
| Purchases | `user_orders_v2` | Cursor, default 20/max 50 | User-scoped, server-side status filtering and grouped payment totals |
| Selected purchase | `get_order_details_v2` | Not applicable | User-scoped; one order, project summary, and plot |
| Order transactions | `get_order_transactions_v2` | Cursor, default 20/max 50 | Loaded only when the transactions tab is opened |
| Search | `searchproperty_v2` | Page, default 20/max 50 | Minimum two characters, ranked exact/prefix matches, full-text support |
| Profile | `profile_summary` | Not applicable | User details, unread notifications, and purchase counts |

All successful responses use:

```json
{
  "status_code": "00",
  "data": {},
  "message": "..."
}
```

Cursor consumers send the returned opaque `pagination.next_cursor` unchanged. They must stop when `pagination.has_more` is false. Cursor pagination is used for plots, likes, orders, and transactions because it avoids increasingly expensive offsets and remains stable while rows are inserted.

## Project loading sequence

The project screen requests `getproperty_summary` and the first `getpropertyplots_v2` page concurrently. The summary renders independently of the plot list. Plot pages append when the user nears the end of the scroll area.

Default plot requests show available plots. The mini app also provides:

- an `all`/`available` status filter;
- a 350 ms debounced plot-number prefix search;
- a stale-response guard so an older request cannot replace newer filter results;
- an explicit empty, loading, and end-of-list state.

## Query and payload rules

- Screen endpoints return projections, not generic nested models.
- Project cards contain a cover image path, counts, location, and minimum prices; they do not contain all plots or all images.
- List relationships are bulk-loaded and keyed in memory. No query is issued inside a row-mapping loop.
- Payment totals are grouped by reference in one query for an order page.
- Selected-order authorization is enforced in the database query with `userid`.
- Transaction history returns only fields rendered by the mini app.
- Plot availability is checked under a row lock during order creation so concurrent checkouts cannot buy the same plot.

## Cache policy

The server caches only shared, relatively stable data:

- `landing:mini:v2`: 5 minutes;
- `project:{id}:summary:v2`: 15 minutes;
- `project:{id}:order-summary:v2`: 15 minutes, so a buyer can still view a purchased project if it is later hidden from the catalogue.

User-specific liked state is never stored in the shared cache. It is attached after the shared value is read.

Changes to a property, plot, image, pricing row, or visit time dispatch `InvalidateProjectReadCache` after the database transaction commits. The job clears project-summary, landing, and legacy plot cache keys. Queue workers must therefore be running in every environment. The finite TTL remains a fallback if invalidation is delayed.

The mini app uses a five-minute, user-scoped stale-while-revalidate cache for first-screen landing, project-summary, liked, purchase, and selected-order data. Like, order, and payment mutations clear the relevant local entries immediately.

## Database indexes

Migration `2026_09_28_000001_add_read_api_indexes.php` adds indexes matching the new access paths:

- visible/priced/live projects by created date and popularity;
- project + plot status + cursor id;
- project + land use + cursor id;
- project + plot number;
- user + like cursor and user + project;
- user + order status + cursor id, order reference, and plot + active status;
- payment reference + cursor id;
- project images and project pricing lookups;
- a full-text index over project name and tags on MySQL/PostgreSQL.

Run the migration before directing client traffic to the v2 search endpoint because the endpoint uses the full-text index on supported database drivers.

## Observability

`api.performance` samples RPC calls and writes structured `api_performance` log records containing:

- operation name;
- total duration;
- query count and summed query duration;
- response bytes;
- status, project id, and order id where applicable.

Configure sampling with `API_PERFORMANCE_SAMPLE_RATE` (`0` disables it, `1` records every call, default `0.1`). Never log access tokens or full request/response bodies in this middleware.

Suggested initial service objectives:

| Operation | p95 target | Query target | Payload target |
| --- | ---: | ---: | ---: |
| First plot page | < 500 ms | <= 5 | < 100 KB |
| Project summary | < 500 ms uncached / < 150 ms cached | <= 10 uncached | < 250 KB |
| Landing | < 400 ms uncached / < 150 ms cached | <= 8 uncached | < 250 KB |
| Likes/orders first page | < 500 ms | <= 8 | < 200 KB |
| Search | < 700 ms | <= 8 | < 200 KB |

Tune targets after one week of production measurements rather than hiding regressions by increasing cache TTLs.

## Deployment order

1. Back up the database and confirm no migration with the same index names exists.
2. Deploy the API code while legacy operations remain available.
3. Run `php artisan migrate --force`.
4. Restart PHP workers and ensure Redis/cache and queue workers are healthy.
5. Set `API_PERFORMANCE_SAMPLE_RATE=1` during a short controlled smoke test, then return it to the desired production rate.
6. Smoke-test every v2 RPC with two different users, including an attempt to access the other user's order.
7. Deploy the mini app and watch latency, query count, error rate, payload size, cache health, and queue lag.
8. Keep the legacy RPC methods during the observation window. Roll back the mini app first if needed; the additive API remains backward compatible.

## Verification

`MiniAppReadServiceTest` covers non-overlapping bounded plot cursor pages and selected-order user scoping/payload shape. Run:

```bash
composer install
php artisan test --filter=MiniAppReadServiceTest
```

Before running the suite, confirm `phpunit.xml` points at an isolated disposable test database. `RefreshDatabase` applies and rolls back the schema; never aim it at staging or production.

Also test on a staging copy with at least 1,000 plots in one project. Verify that the first plot response still contains only the configured page size and that query count does not increase with the total number of plots.
