Feat: Add tag browser page at /tags with count-sorted tag list and library deep-link
- Extends GET /api/v1/tags with sort=count_desc and min_count query params - New TagsComponent at /tags (public, no auth guard) shows all tags sorted by image count - Clicking a tag navigates to /?tags=<name> for a pre-filtered library view - LibraryComponent reads ?tags= query param on init to support deep-linking from tag browser - Library header gains a "Browse tags" link to /tags for discoverability - All 15 TDD tasks complete; ruff, ng lint, and ng build clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -12,15 +11,19 @@ class ImageRepository:
|
||||
def __init__(self, session: AsyncSession) -> None:
|
||||
self._session = session
|
||||
|
||||
async def get_by_hash(self, hash_hex: str) -> Optional[Image]:
|
||||
async def get_by_hash(self, hash_hex: str) -> Image | None:
|
||||
result = await self._session.execute(
|
||||
select(Image).where(Image.hash == hash_hex).options(selectinload(Image.image_tags).selectinload(ImageTag.tag))
|
||||
select(Image)
|
||||
.where(Image.hash == hash_hex)
|
||||
.options(selectinload(Image.image_tags).selectinload(ImageTag.tag))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_id(self, image_id: uuid.UUID) -> Optional[Image]:
|
||||
async def get_by_id(self, image_id: uuid.UUID) -> Image | None:
|
||||
result = await self._session.execute(
|
||||
select(Image).where(Image.id == image_id).options(selectinload(Image.image_tags).selectinload(ImageTag.tag))
|
||||
select(Image)
|
||||
.where(Image.id == image_id)
|
||||
.options(selectinload(Image.image_tags).selectinload(ImageTag.tag))
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@@ -57,7 +60,7 @@ class ImageRepository:
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
) -> tuple[list[Image], int]:
|
||||
from sqlalchemy import func, and_
|
||||
from sqlalchemy import func
|
||||
|
||||
base_query = select(Image).options(
|
||||
selectinload(Image.image_tags).selectinload(ImageTag.tag)
|
||||
|
||||
Reference in New Issue
Block a user