88 lines
3.1 KiB
Markdown
88 lines
3.1 KiB
Markdown
# Fresh
|
|
|
|
## Route
|
|
|
|
- URL: `GET /discover/fresh`
|
|
- Controller: `App\Http\Controllers\Web\DiscoverController::fresh()`
|
|
- Service: `App\Services\ArtworkSearchService::discoverFresh()`
|
|
|
|
## What the page reads
|
|
|
|
Fresh is Meilisearch-backed, then hydrated from MySQL.
|
|
|
|
The page does not directly query `artworks` for ranking order.
|
|
It relies on the search index being up to date.
|
|
|
|
If the search-backed result comes back empty, the controller falls back to a direct MySQL query ordered by `published_at DESC, id DESC` so the page does not render blank while search is catching up.
|
|
|
|
## Search query
|
|
|
|
`discoverFresh()` uses:
|
|
|
|
- filter: `is_public = true AND is_approved = true`
|
|
- sort:
|
|
- `published_at_ts:desc`
|
|
|
|
`published_at_ts` is a numeric timestamp field stored in the search document specifically so same-day uploads can be ordered correctly by hour and minute.
|
|
|
|
## Why the dedicated timestamp field exists
|
|
|
|
Historically, the index sorted by a date-only `created_at` string, which meant all uploads on the same calendar day could collapse to the same sort value.
|
|
|
|
The current implementation uses `published_at_ts` to preserve intra-day ordering and avoid newer uploads being buried behind older uploads from the same date.
|
|
|
|
## Page behavior
|
|
|
|
Fresh now uses the raw newest-first search result without any curated blending or grid filler injection.
|
|
|
|
That means:
|
|
|
|
- page 1 is not mixed with spotlight content
|
|
- page 1 is not padded with older trending artworks
|
|
- deeper pages follow the same ordering model as page 1
|
|
|
|
If older artworks appear near the top, the likely causes are stale search documents or stale cache, not intentional feed mixing.
|
|
|
|
If the page renders completely empty even though recent public artworks exist, the DB fallback should populate it. A blank page after that points to a real data visibility problem, not just search freshness.
|
|
|
|
## Data sources
|
|
|
|
Ranking eligibility depends on:
|
|
|
|
- `is_public`
|
|
- `is_approved`
|
|
- presence in Meilisearch
|
|
- `published_at_ts` in the indexed document
|
|
|
|
Hydration reads full rows from MySQL after the search query returns IDs.
|
|
|
|
When the fallback path is used, the page is served directly from MySQL and does not require Meilisearch for that request.
|
|
|
|
## Relevant jobs and schedules
|
|
|
|
Fresh does not have a dedicated score calculation job.
|
|
It depends on publication and indexing freshness.
|
|
|
|
Relevant active schedules:
|
|
|
|
- `artworks:publish-scheduled` every minute
|
|
- `skinbase:flush-redis-stats` every 5 minutes (not for ordering, but for displayed stats freshness)
|
|
|
|
Index freshness depends on:
|
|
|
|
- normal Scout indexing from artwork updates
|
|
- scheduled-publication indexing after an artwork transitions from scheduled to published
|
|
- manual/full imports after search-document schema changes
|
|
|
|
## Cache behavior
|
|
|
|
- Cache key: `discover.fresh.{page}`
|
|
- TTL: 300 seconds
|
|
|
|
## Notes
|
|
|
|
- Fresh is the page most sensitive to stale indexing because it is supposed to surface the latest publish action immediately.
|
|
- If an artwork is public in MySQL but absent from Fresh, the usual causes are:
|
|
- it has not been indexed yet
|
|
- app cache has not expired yet
|
|
- the artwork is not actually public and approved at the same time |