feat: safe deletion, album/asset delete, trash, README update

- volume-aware deletion: read-only volumes remove DB only, writable
  volumes soft-delete to trash with configurable grace period
- trash page with restore, worker purge sweep (TRASH_RETENTION_DAYS)
- album delete endpoint + sidebar trash icon
- asset delete from timeline selection toolbar
- all listing queries exclude trashed assets (deleted_at IS NULL)
- timeline ordered by EXIF capture date, date-summary endpoint
- README rewritten with features, setup, full env var table
This commit is contained in:
2026-06-01 01:57:53 +02:00
parent 957737ac9b
commit 0077caa743
36 changed files with 752 additions and 125 deletions

View File

@@ -1,16 +1,35 @@
"use client"
import { useMemo } from "react"
import { useQueryClient } from "@tanstack/react-query"
import { useTimeline, useDateSummary } from "@/hooks/use-timeline"
import { groupByDate } from "@/lib/timeline"
import { PhotoGrid } from "@/components/photo-grid"
import { DateScrubber } from "@/components/date-scrubber"
import api from "@/lib/api"
import { toast } from "sonner"
export default function TimelinePage() {
const qc = useQueryClient()
const { assets, isLoading, hasMore, loadMore, total } = useTimeline()
const { data: dateSummary } = useDateSummary()
const groups = useMemo(() => groupByDate(assets), [assets])
const handleDeleteAssets = async (ids: string[]) => {
let deleted = 0
for (const id of ids) {
try {
await api.delete(`/assets/${id}`)
deleted++
} catch { /* skip */ }
}
if (deleted > 0) {
toast.success(`Deleted ${deleted} photo(s)`)
qc.invalidateQueries({ queryKey: ["timeline"] })
qc.invalidateQueries({ queryKey: ["date-summary"] })
}
}
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
@@ -27,6 +46,7 @@ export default function TimelinePage() {
isLoading={isLoading}
hasMore={hasMore}
onLoadMore={() => loadMore()}
onDeleteAssets={handleDeleteAssets}
/>
<DateScrubber dates={dateSummary ?? []} />
</div>