- verdicts table (per link, like ratings), PUT verdict endpoint, typed Verdict enum - share page: thumbs up/down, verdict filter with counts, view-scoped selection bar - lightbox: per-page shortcut table (P/X/U, 1-5/0, S), ? help overlay, action toast when a keyboard vote auto-advances - shared useLightbox hook; single keydown subscription reading live state via ref - album view: per-link thumbs and vote counts; share list shows accept/reject totals - feedback queries deduped and run concurrently; verdict counts in one scan
This commit is contained in:
+20
-9
@@ -23,6 +23,8 @@ struct ShareAdminRow {
|
||||
created_at: DateTime<Utc>,
|
||||
rating_count: i64,
|
||||
tag_count: i64,
|
||||
accept_count: i64,
|
||||
reject_count: i64,
|
||||
}
|
||||
|
||||
fn share_json(state: &AppState, row: &ShareAdminRow) -> serde_json::Value {
|
||||
@@ -38,20 +40,30 @@ fn share_json(state: &AppState, row: &ShareAdminRow) -> serde_json::Value {
|
||||
"created_at": row.created_at,
|
||||
"rating_count": row.rating_count,
|
||||
"tag_count": row.tag_count,
|
||||
"accept_count": row.accept_count,
|
||||
"reject_count": row.reject_count,
|
||||
})
|
||||
}
|
||||
|
||||
const SHARE_COLUMNS: &str = "s.id, s.token, s.label, s.password_hash, s.allow_download,
|
||||
// Aggregates run as laterals so each feedback table is scanned once per
|
||||
// share (the verdict lateral yields both counts from a single pass).
|
||||
const SHARE_SELECT: &str = "select s.id, s.token, s.label, s.password_hash, s.allow_download,
|
||||
s.expires_at, s.locked_until, s.created_at,
|
||||
(select count(*) from ratings r where r.share_id = s.id) as rating_count,
|
||||
(select count(*) from tags t where t.share_id = s.id) as tag_count";
|
||||
rc.rating_count, tc.tag_count, vc.accept_count, vc.reject_count
|
||||
from shares s
|
||||
cross join lateral (select count(*) as rating_count from ratings r where r.share_id = s.id) rc
|
||||
cross join lateral (select count(*) as tag_count from tags t where t.share_id = s.id) tc
|
||||
cross join lateral (
|
||||
select count(*) filter (where v.verdict = 'accept') as accept_count,
|
||||
count(*) filter (where v.verdict = 'reject') as reject_count
|
||||
from verdicts v where v.share_id = s.id) vc";
|
||||
|
||||
pub async fn list(
|
||||
State(state): State<AppState>,
|
||||
Path(album_id): Path<Uuid>,
|
||||
) -> ApiResult<Json<Vec<serde_json::Value>>> {
|
||||
let rows: Vec<ShareAdminRow> = sqlx::query_as(&format!(
|
||||
"select {SHARE_COLUMNS} from shares s where s.album_id = $1 order by s.created_at desc"
|
||||
"{SHARE_SELECT} where s.album_id = $1 order by s.created_at desc"
|
||||
))
|
||||
.bind(album_id)
|
||||
.fetch_all(&state.db)
|
||||
@@ -119,11 +131,10 @@ pub async fn create(
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
|
||||
let row: ShareAdminRow =
|
||||
sqlx::query_as(&format!("select {SHARE_COLUMNS} from shares s where s.id = $1"))
|
||||
.bind(share_id)
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
let row: ShareAdminRow = sqlx::query_as(&format!("{SHARE_SELECT} where s.id = $1"))
|
||||
.bind(share_id)
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
Ok(Json(share_json(&state, &row)))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user