SeaORM: items and read state
The item list, its counts, filters, sorts and search, positions, pins and mark-all-read move to SeaORM, as SQL written for both databases: - Parameters are gathered as the SQL is written (Args), so only what a statement uses is bound. rusqlite needed every one mentioned, hence the old `?1 IS NULL` and `?2 = ''`; Postgres refuses a parameter it cannot type. - Yes/no columns are tested as booleans (NOT coalesce(s.read, false)) and written as true, not 1; SQLite reads true and false as 1 and 0. - The last tiebreak of the sort is the guid, not SQLite's rowid, which Postgres lacks. Only items with the same date change places. - set_position names entry_state.duration beside excluded.duration. - The status callback on the control socket returns a future, as the counts are now a query. Checked on a copy of production against the live server: 42 of 48 lists identical; the other six differ only in how ties fall, or because the test daemon cleared paths to files this machine does not have. Run on the same file, every filter's count matches the old SQL exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
22
src/web.rs
22
src/web.rs
@@ -740,7 +740,7 @@ async fn feeds(
|
||||
last_error: s.last_error,
|
||||
entries: s.entries,
|
||||
downloaded: s.downloaded,
|
||||
unread: state.ctx.db.unread_count(user.id, id)?,
|
||||
unread: state.ctx.db.unread_count(user.id, id).await?,
|
||||
subscribers: counts.get(id).copied().unwrap_or(0),
|
||||
pinned: pinned.contains(id),
|
||||
});
|
||||
@@ -1124,7 +1124,7 @@ async fn entries(
|
||||
user: crate::db::User,
|
||||
Query(page): Query<Page>,
|
||||
) -> Result<Json<EntryPage>, ApiError> {
|
||||
entry_page(&state, user.id, Some(&id), &page)
|
||||
entry_page(&state, user.id, Some(&id), &page).await
|
||||
}
|
||||
|
||||
/// Every subscribed feed's items together, newest first: All Subscriptions.
|
||||
@@ -1133,11 +1133,11 @@ async fn all_entries(
|
||||
user: crate::db::User,
|
||||
Query(page): Query<Page>,
|
||||
) -> Result<Json<EntryPage>, ApiError> {
|
||||
entry_page(&state, user.id, None, &page)
|
||||
entry_page(&state, user.id, None, &page).await
|
||||
}
|
||||
|
||||
/// One feed's page of items, or every subscribed feed's when `feed` is None.
|
||||
fn entry_page(
|
||||
async fn entry_page(
|
||||
state: &WebState,
|
||||
user_id: i64,
|
||||
feed: Option<&str>,
|
||||
@@ -1153,14 +1153,14 @@ fn entry_page(
|
||||
filter != crate::db::Filter::InProgress,
|
||||
);
|
||||
let mut rows =
|
||||
db.entries_in(user_id, feed, filter, search, page.offset, page.limit.clamp(1, 200), &order)?;
|
||||
db.entries_in(user_id, feed, filter, search, page.offset, page.limit.clamp(1, 200), &order).await?;
|
||||
let mut sanitizer = feed_sanitizer();
|
||||
for row in &mut rows {
|
||||
if let Some(d) = &row.description {
|
||||
row.description = Some(clean_description(&mut sanitizer, d, row.link.as_deref()));
|
||||
}
|
||||
}
|
||||
let total = db.count_in(user_id, feed, filter, search)?;
|
||||
let total = db.count_in(user_id, feed, filter, search).await?;
|
||||
Ok(Json(EntryPage { total, entries: rows }))
|
||||
}
|
||||
|
||||
@@ -1440,10 +1440,10 @@ async fn set_flags(
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
use crate::db::EntryFlag;
|
||||
if let Some(v) = body.read {
|
||||
state.ctx.db.set_entry_flag(user.id, &feed_id, &guid, EntryFlag::Read, v)?;
|
||||
state.ctx.db.set_entry_flag(user.id, &feed_id, &guid, EntryFlag::Read, v).await?;
|
||||
}
|
||||
if let Some(v) = body.flagged {
|
||||
state.ctx.db.set_entry_flag(user.id, &feed_id, &guid, EntryFlag::Flagged, v)?;
|
||||
state.ctx.db.set_entry_flag(user.id, &feed_id, &guid, EntryFlag::Flagged, v).await?;
|
||||
}
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
@@ -1598,7 +1598,7 @@ async fn set_position(
|
||||
user: crate::db::User,
|
||||
Json(body): Json<Position>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
state.ctx.db.set_position(user.id, &feed_id, &guid, body.secs, body.duration)?;
|
||||
state.ctx.db.set_position(user.id, &feed_id, &guid, body.secs, body.duration).await?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@@ -1615,7 +1615,7 @@ async fn read_all(
|
||||
.filter(|s| s.cfg.group.as_deref() == Some(id.as_str()))
|
||||
.map(|s| s.id),
|
||||
);
|
||||
let n = state.ctx.db.mark_all_read(user.id, &ids)?;
|
||||
let n = state.ctx.db.mark_all_read(user.id, &ids).await?;
|
||||
Ok(Json(serde_json::json!({ "marked": n })))
|
||||
}
|
||||
|
||||
@@ -1627,7 +1627,7 @@ async fn read_all_mine(
|
||||
) -> Result<Json<serde_json::Value>, ApiError> {
|
||||
let ids: Vec<String> =
|
||||
state.ctx.db.subscriptions_for(user.id).await?.into_iter().map(|s| s.feed_id).collect();
|
||||
let n = state.ctx.db.mark_all_read(user.id, &ids)?;
|
||||
let n = state.ctx.db.mark_all_read(user.id, &ids).await?;
|
||||
Ok(Json(serde_json::json!({ "marked": n })))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user