- 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:
+40
-2
@@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::{ApiError, ApiResult};
|
||||
use crate::models::{PhotoStatus, Share};
|
||||
use crate::models::{PhotoStatus, Share, Verdict};
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn load_share(state: &AppState, token: &str) -> Result<Share, ApiError> {
|
||||
@@ -137,6 +137,7 @@ struct ClientPhotoRow {
|
||||
taken_at: Option<DateTime<Utc>>,
|
||||
processed_at: Option<DateTime<Utc>>,
|
||||
my_rating: Option<i32>,
|
||||
my_verdict: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -185,9 +186,10 @@ pub async fn get_share(
|
||||
let jar = grant_access(&state, jar, share.id);
|
||||
|
||||
let rows: Vec<ClientPhotoRow> = sqlx::query_as(
|
||||
"select p.id, p.filename, p.size_bytes, p.width, p.height, p.taken_at, p.processed_at, r.rating as my_rating
|
||||
"select p.id, p.filename, p.size_bytes, p.width, p.height, p.taken_at, p.processed_at, r.rating as my_rating, v.verdict as my_verdict
|
||||
from photos p
|
||||
left join ratings r on r.photo_id = p.id and r.share_id = $2
|
||||
left join verdicts v on v.photo_id = p.id and v.share_id = $2
|
||||
where p.album_id = $1 and p.status = $3
|
||||
order by coalesce(p.taken_at, p.created_at), p.filename",
|
||||
)
|
||||
@@ -352,6 +354,42 @@ pub async fn set_rating(
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct VerdictBody {
|
||||
verdict: Option<Verdict>,
|
||||
}
|
||||
|
||||
pub async fn set_verdict(
|
||||
State(state): State<AppState>,
|
||||
Path((token, photo_id)): Path<(String, Uuid)>,
|
||||
jar: SignedCookieJar,
|
||||
Json(body): Json<VerdictBody>,
|
||||
) -> ApiResult<Json<serde_json::Value>> {
|
||||
let share = share_photo(&state, &jar, &token, photo_id).await?;
|
||||
match body.verdict {
|
||||
None => {
|
||||
sqlx::query("delete from verdicts where share_id = $1 and photo_id = $2")
|
||||
.bind(share.id)
|
||||
.bind(photo_id)
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
}
|
||||
Some(verdict) => {
|
||||
sqlx::query(
|
||||
"insert into verdicts (share_id, photo_id, verdict) values ($1, $2, $3)
|
||||
on conflict (share_id, photo_id)
|
||||
do update set verdict = excluded.verdict, updated_at = now()",
|
||||
)
|
||||
.bind(share.id)
|
||||
.bind(photo_id)
|
||||
.bind(verdict.as_str())
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Ok(Json(serde_json::json!({ "ok": true })))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TagsBody {
|
||||
tags: Vec<String>,
|
||||
|
||||
Reference in New Issue
Block a user