US01-06: Review Inventory and Duplicates in the Browser (#51)

This commit was merged in pull request #51.
This commit is contained in:
2026-07-15 21:44:48 +02:00
parent 98e9cb2988
commit 4b4c05339e
15 changed files with 1110 additions and 4 deletions

View File

@@ -30,7 +30,7 @@ from enum import Enum
from pathlib import Path
from typing import Iterable
from sqlalchemy import select
from sqlalchemy import func, select
from sqlalchemy.orm import Session, sessionmaker
from photo_pipeline import path_policy
@@ -55,6 +55,21 @@ class ScanResult:
asset_ids: dict[str, str] = field(default_factory=dict) # path -> asset id
def _asset_dict(asset: Asset) -> dict:
return {
"id": asset.id,
"current_path": asset.current_path,
"availability_state": asset.availability_state,
"byte_size": asset.byte_size,
"current_sha256": asset.current_sha256,
"pixel_sha256": asset.pixel_sha256,
"phash": asset.phash,
"canonical_asset_id": asset.canonical_asset_id,
"missing": asset.missing_at is not None,
"state_version": asset.state_version,
}
class InventoryService:
def __init__(self, session_factory: sessionmaker) -> None:
self._session_factory = session_factory
@@ -101,6 +116,30 @@ class InventoryService:
result.counts = dict(Counter(result.occurrences.values()))
return result
def list_assets(
self,
*,
limit: int = 50,
offset: int = 0,
availability: str | None = None,
query: str | None = None,
) -> dict:
"""Paged, filterable asset listing for the inventory UI (read-only)."""
limit = max(1, min(limit, 200))
offset = max(0, offset)
with self._session_factory() as session:
stmt = select(Asset)
if availability:
stmt = stmt.where(Asset.availability_state == availability)
if query:
stmt = stmt.where(Asset.current_path.like(f"%{query}%"))
total = session.scalar(select(func.count()).select_from(stmt.subquery()))
rows = session.execute(
stmt.order_by(Asset.current_path).limit(limit).offset(offset)
).scalars()
items = [_asset_dict(a) for a in rows]
return {"items": items, "total": int(total or 0), "limit": limit, "offset": offset}
def _reconcile_one(
self,
session: Session,