feat(api): GET /dictionaries/{name}/snapshots — record-change buckets (backend #2)
Backend #2 из pending-endpoints brief. Frontend TimeTravelModal использует этот endpoint для slider marks — каждая точка = bucket где минимум одна запись validFrom попадает. GET /api/v1/dictionaries/{name}/snapshots?from=...&to=...&granularity=day Implementation: - DictionaryRecordRepository.findSnapshotBuckets — native SQL date_trunc GROUP BY bucket, ORDER BY bucket DESC. scope filter через text[] из CSV. - SnapshotsService — gates dict existence/scope, validates granularity (hour/day/week) + window, formats RU label (1 апр / сейчас), reverses список чтобы oldest first (UI читает слева-направо). - DictionaryChangelogController новый endpoint /snapshots. Errors: - 404 dictionary_not_found - 400 invalid_granularity (not in {hour,day,week}) - 400 invalid_window (from >= to) Frontend impact: TimeTravelModal slider marks переходит с recordTimestamps derived data на реальный API. ~1h frontend swap.
This commit is contained in:
+32
@@ -146,4 +146,36 @@ public interface DictionaryRecordRepository extends JpaRepository<DictionaryReco
|
||||
@Param("scopesCsv") String[] scopesCsv,
|
||||
@Param("at") OffsetDateTime at,
|
||||
@Param("polygonGeoJson") String polygonGeoJson);
|
||||
|
||||
/**
|
||||
* Bucketed list snapshot points: timestamps когда records появлялись
|
||||
* (validFrom) в заданном time window. Используется для TimeTravel
|
||||
* slider marks на admin-UI — каждая mark = "interesting" moment где
|
||||
* минимум одна запись changed.
|
||||
*
|
||||
* <p>Granularity: PostgreSQL date_trunc unit ({@code hour}, {@code day},
|
||||
* {@code week}). Возвращает Object[] = {bucket OffsetDateTime,
|
||||
* changedSinceLast Long}. Caller'а потом мапит на DTO.
|
||||
*
|
||||
* @param scopes — CSV array (passed как text[] для unnest in WHERE), скоупы
|
||||
* которые caller видит. EMPTY → no records visible (caller
|
||||
* anonymous и scope filter empty).
|
||||
*/
|
||||
@Query(value = """
|
||||
SELECT date_trunc(:granularity, valid_from)::timestamptz AS bucket,
|
||||
count(*) AS changed_since_last
|
||||
FROM dictionary_records
|
||||
WHERE dictionary_id = :dictionaryId
|
||||
AND data_scope = ANY (string_to_array(:scopesCsv, ','))
|
||||
AND valid_from >= :from
|
||||
AND valid_from < :to
|
||||
GROUP BY bucket
|
||||
ORDER BY bucket DESC
|
||||
""", nativeQuery = true)
|
||||
List<Object[]> findSnapshotBuckets(
|
||||
@Param("dictionaryId") UUID dictionaryId,
|
||||
@Param("scopesCsv") String scopesCsv,
|
||||
@Param("from") OffsetDateTime from,
|
||||
@Param("to") OffsetDateTime to,
|
||||
@Param("granularity") String granularity);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user