Refactor insights handling and error encoding in presentation layer
- Updated insights analyzer tests to use Utc for date handling. - Added timezone support for insights retrieval, allowing insights to be bucketed by the user's local time. - Introduced `encode_domain_error` for better error handling in API responses. - Modified various handlers to utilize the new error encoding function. - Enhanced insights API to reject unknown time zones and return appropriate errors. - Updated frontend components to handle insights data with timezone awareness. - Improved localization for insights sample counts.
This commit is contained in:
@@ -30,7 +30,7 @@ impl InsightsQuery for PostgresInsightsQuery {
|
||||
to_char(watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at, \
|
||||
watch_medium \
|
||||
FROM reviews \
|
||||
WHERE user_id = $1 \
|
||||
WHERE user_id = $1 AND remote_actor_url IS NULL \
|
||||
ORDER BY watched_at ASC",
|
||||
)
|
||||
.bind(user_id.value().to_string())
|
||||
|
||||
@@ -8,8 +8,6 @@ use sqlx::{Row, SqlitePool};
|
||||
|
||||
use adapter_common::{parse_datetime, parse_uuid};
|
||||
|
||||
use crate::wrapup::fetch_genres_sqlite;
|
||||
|
||||
/// SQLite read side of Insights (ADR-0010). Deliberately thin: one query for
|
||||
/// review basics, one for genres — every threshold and comparison lives in
|
||||
/// the pure analyzer, not here.
|
||||
@@ -29,7 +27,7 @@ impl InsightsQuery for SqliteInsightsQuery {
|
||||
let rows = sqlx::query(
|
||||
"SELECT movie_id, rating, watched_at, watch_medium \
|
||||
FROM reviews \
|
||||
WHERE user_id = ? \
|
||||
WHERE user_id = ? AND remote_actor_url IS NULL \
|
||||
ORDER BY watched_at ASC",
|
||||
)
|
||||
.bind(user_id.value().to_string())
|
||||
@@ -37,20 +35,32 @@ impl InsightsQuery for SqliteInsightsQuery {
|
||||
.await
|
||||
.map_err(adapter_common::map_sqlx_error)?;
|
||||
|
||||
let mut movie_ids: Vec<String> = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for row in &rows {
|
||||
// Subquery rather than an `IN (?, …)` bind list: a whole diary can
|
||||
// exceed SQLite's bound-parameter limit.
|
||||
let genre_rows = sqlx::query(
|
||||
"SELECT movie_id, name FROM movie_genres \
|
||||
WHERE movie_id IN ( \
|
||||
SELECT movie_id FROM reviews \
|
||||
WHERE user_id = ? AND remote_actor_url IS NULL \
|
||||
) \
|
||||
ORDER BY name",
|
||||
)
|
||||
.bind(user_id.value().to_string())
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(adapter_common::map_sqlx_error)?;
|
||||
|
||||
let mut genres_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for row in genre_rows {
|
||||
let mid: String = row
|
||||
.try_get("movie_id")
|
||||
.map_err(adapter_common::map_sqlx_error)?;
|
||||
if seen.insert(mid.clone()) {
|
||||
movie_ids.push(mid);
|
||||
}
|
||||
let name: String = row
|
||||
.try_get("name")
|
||||
.map_err(adapter_common::map_sqlx_error)?;
|
||||
genres_map.entry(mid).or_default().push(name);
|
||||
}
|
||||
|
||||
let genres_map: HashMap<String, Vec<String>> =
|
||||
fetch_genres_sqlite(&self.pool, &movie_ids).await?;
|
||||
|
||||
let mut result = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
let movie_id: String = row
|
||||
|
||||
Reference in New Issue
Block a user