feat: "On this day" memories — photos from previous years
Add a Memories view that surfaces photos taken on the current date in previous years (like Google Photos / Immich). Only uses EXIF-sourced dates to avoid false matches from filesystem timestamps. - Backend: GET /api/v1/photos/memories returns groups by year, up to 12 photos each, filtered to non-discarded/non-hidden EXIF dates - Frontend: MemoriesView with year-grouped thumbnail grid - Sidebar: new "Memories" nav item with clock icon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -352,6 +352,74 @@ async def list_photos_with_gps(
|
||||
]
|
||||
|
||||
|
||||
@router.get("/memories")
|
||||
async def get_memories(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""'On this day' — photos taken on this date in previous years.
|
||||
|
||||
Returns groups keyed by year, each with up to 12 photos. Only
|
||||
considers non-discarded, non-hidden photos with an EXIF-sourced
|
||||
taken_at (no filesystem-guessed dates to avoid false matches).
|
||||
"""
|
||||
from sqlalchemy import extract
|
||||
|
||||
today = datetime.now().date()
|
||||
|
||||
result = await db.execute(
|
||||
select(
|
||||
Photo.id,
|
||||
Photo.filename,
|
||||
Photo.taken_at,
|
||||
Photo.thumb_small,
|
||||
Photo.thumb_medium,
|
||||
Photo.media_type,
|
||||
Photo.width,
|
||||
Photo.height,
|
||||
Photo.rating,
|
||||
)
|
||||
.where(
|
||||
Photo.user_id == current_user.id,
|
||||
Photo.is_discarded.is_(False),
|
||||
Photo.is_hidden.is_(False),
|
||||
Photo.taken_at.is_not(None),
|
||||
Photo.taken_at_source == "exif",
|
||||
extract("month", Photo.taken_at) == today.month,
|
||||
extract("day", Photo.taken_at) == today.day,
|
||||
extract("year", Photo.taken_at) < today.year,
|
||||
)
|
||||
.order_by(Photo.taken_at.desc())
|
||||
)
|
||||
rows = result.all()
|
||||
|
||||
# Group by year
|
||||
years: dict[int, list] = {}
|
||||
for row in rows:
|
||||
year = row.taken_at.year
|
||||
group = years.setdefault(year, [])
|
||||
if len(group) >= 12:
|
||||
continue
|
||||
group.append({
|
||||
"id": row.id,
|
||||
"filename": row.filename,
|
||||
"taken_at": row.taken_at.isoformat(),
|
||||
"thumb_small": row.thumb_small,
|
||||
"thumb_medium": row.thumb_medium,
|
||||
"media_type": row.media_type,
|
||||
"width": row.width,
|
||||
"height": row.height,
|
||||
"rating": row.rating,
|
||||
})
|
||||
|
||||
memories = [
|
||||
{"year": year, "years_ago": today.year - year, "photos": photos}
|
||||
for year, photos in sorted(years.items())
|
||||
]
|
||||
|
||||
return {"date": today.isoformat(), "memories": memories}
|
||||
|
||||
|
||||
@router.get("/{photo_id}")
|
||||
async def get_photo(
|
||||
photo_id: str,
|
||||
|
||||
Reference in New Issue
Block a user