Bulk delete, editable share links, gallery filter fix
ci / docker (push) Successful in 9s

- shift-click selects ranges in the gallery; selection bar gains
  'Delete N' with a single confirm (POST /api/photos/delete)
- PATCH /api/shares/{id}: allow_download and expiry editable in place,
  token and client feedback preserved
- Gallery: re-attach ResizeObserver via callback ref — after a filter
  with zero matches the gallery stayed blank at width 0
This commit is contained in:
2026-07-17 16:30:26 +02:00
parent 30d0b3064e
commit 5cb00f3ef0
7 changed files with 159 additions and 16 deletions
+43
View File
@@ -138,6 +138,49 @@ pub async fn create(
Ok(Json(share_json(&state, &row)))
}
/// Distinguishes an absent JSON field (keep current value) from an explicit
/// null (clear the expiry): absent → None, present → Some(inner).
fn double_option<'de, D>(de: D) -> Result<Option<Option<DateTime<Utc>>>, D::Error>
where
D: serde::Deserializer<'de>,
{
serde::Deserialize::deserialize(de).map(Some)
}
#[derive(Deserialize)]
pub struct UpdateShare {
allow_download: Option<bool>,
#[serde(default, deserialize_with = "double_option")]
expires_at: Option<Option<DateTime<Utc>>>,
}
pub async fn update(
State(state): State<AppState>,
Path(share_id): Path<Uuid>,
Json(body): Json<UpdateShare>,
) -> ApiResult<Json<serde_json::Value>> {
let updated = sqlx::query(
"update shares set
allow_download = coalesce($2, allow_download),
expires_at = case when $3 then $4 else expires_at end
where id = $1",
)
.bind(share_id)
.bind(body.allow_download)
.bind(body.expires_at.is_some())
.bind(body.expires_at.flatten())
.execute(&state.db)
.await?;
if updated.rows_affected() == 0 {
return Err(ApiError::not_found());
}
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)))
}
/// Clear a share's password-lockout state (e.g. after a client fat-fingered
/// their way into the 15-minute lock).
pub async fn reset_lock(