neue Archivfunktion und Optimierung an Artikelbearbeitung
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, status
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.deps import DbSession, VerifiedUser
|
||||
@@ -17,13 +19,38 @@ from app.schemas_shopping import (
|
||||
MemberRoleIn,
|
||||
)
|
||||
from app.security import utcnow
|
||||
from app.users import months_setting
|
||||
|
||||
router = APIRouter(prefix="/api/lists", tags=["lists"])
|
||||
|
||||
MAX_LISTS_PER_USER = 100
|
||||
|
||||
|
||||
def _to_out(db, lst: ShoppingList, role: str, may_share: bool = False) -> ListOut:
|
||||
ARCHIVE_DELETE_MONTHS_DEFAULT = 12
|
||||
|
||||
|
||||
def _delete_due(db, lst: ShoppingList) -> datetime | None:
|
||||
"""Wann würde die Liste automatisch gelöscht?
|
||||
|
||||
Maßgeblich ist ausschließlich das Archivdatum des EIGENTÜMERS. Dass
|
||||
ein eingeladenes Mitglied die Liste weggeräumt hat, sagt nichts
|
||||
darüber, ob sie noch gebraucht wird.
|
||||
"""
|
||||
owner = db.get(ListMember, (lst.id, lst.owner_id))
|
||||
if owner is None or owner.archived_at is None:
|
||||
return None
|
||||
months = months_setting(
|
||||
db, "archive_delete_months", ARCHIVE_DELETE_MONTHS_DEFAULT
|
||||
)
|
||||
if not months:
|
||||
return None
|
||||
return owner.archived_at + timedelta(days=months * 30)
|
||||
|
||||
|
||||
def _to_out(
|
||||
db, lst: ShoppingList, role: str, may_share: bool = False,
|
||||
archived_at: datetime | None = None,
|
||||
) -> ListOut:
|
||||
count = db.scalar(
|
||||
select(func.count()).select_from(ListMember).where(ListMember.list_id == lst.id)
|
||||
)
|
||||
@@ -31,18 +58,40 @@ def _to_out(db, lst: ShoppingList, role: str, may_share: bool = False) -> ListOu
|
||||
id=lst.id, name=lst.name, owner_id=lst.owner_id, rev=lst.rev,
|
||||
role=role, may_share_public=(role == "owner") or may_share,
|
||||
member_count=count or 0, created_at=lst.created_at,
|
||||
archived=archived_at is not None,
|
||||
archived_at=archived_at,
|
||||
delete_due_at=_delete_due(db, lst) if role == "owner" else None,
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=list[ListOut])
|
||||
def my_lists(db: DbSession, user: VerifiedUser):
|
||||
def my_lists(
|
||||
db: DbSession,
|
||||
user: VerifiedUser,
|
||||
archived: bool | None = Query(
|
||||
default=False,
|
||||
description="False = nur aktive, True = nur archivierte, "
|
||||
"nicht gesetzt = alle",
|
||||
),
|
||||
):
|
||||
rows = db.execute(
|
||||
select(ShoppingList, ListMember.role, ListMember.may_share_public)
|
||||
select(
|
||||
ShoppingList,
|
||||
ListMember.role,
|
||||
ListMember.may_share_public,
|
||||
ListMember.archived_at,
|
||||
)
|
||||
.join(ListMember, ListMember.list_id == ShoppingList.id)
|
||||
.where(ListMember.user_id == user.id, ShoppingList.deleted_at.is_(None))
|
||||
.order_by(ShoppingList.created_at)
|
||||
).all()
|
||||
return [_to_out(db, lst, role, may) for lst, role, may in rows]
|
||||
|
||||
if archived is True:
|
||||
rows = [r for r in rows if r[3] is not None]
|
||||
elif archived is False:
|
||||
rows = [r for r in rows if r[3] is None]
|
||||
|
||||
return [_to_out(db, lst, role, may, at) for lst, role, may, at in rows]
|
||||
|
||||
|
||||
@router.post("", response_model=ListOut, status_code=status.HTTP_201_CREATED)
|
||||
@@ -71,7 +120,40 @@ def create_list(payload: ListCreateIn, db: DbSession, user: VerifiedUser):
|
||||
@router.get("/{list_id}", response_model=ListOut)
|
||||
def read_list(lst: ReadableList, db: DbSession, user: VerifiedUser):
|
||||
member = db.get(ListMember, (lst.id, user.id))
|
||||
return _to_out(db, lst, member.role, member.may_share_public)
|
||||
return _to_out(db, lst, member.role, member.may_share_public, member.archived_at)
|
||||
|
||||
|
||||
@router.post("/{list_id}/archive", response_model=ListOut)
|
||||
def archive_list(lst: ReadableList, db: DbSession, user: VerifiedUser):
|
||||
"""Nimmt die Liste aus der eigenen Übersicht.
|
||||
|
||||
Wirkt nur für die aufrufende Person. Eine gemeinsame Liste
|
||||
wegzuräumen, weil einer sie nicht mehr braucht, wäre ein Übergriff
|
||||
auf die anderen.
|
||||
"""
|
||||
member = db.get(ListMember, (lst.id, user.id))
|
||||
if member.archived_at is None:
|
||||
member.archived_at = utcnow()
|
||||
db.commit()
|
||||
return _to_out(db, lst, member.role, member.may_share_public, member.archived_at)
|
||||
|
||||
|
||||
@router.post("/{list_id}/unarchive", response_model=ListOut)
|
||||
def unarchive_list(lst: ReadableList, db: DbSession, user: VerifiedUser):
|
||||
member = db.get(ListMember, (lst.id, user.id))
|
||||
member.archived_at = None
|
||||
# Zähler zurücksetzen, sonst wandert die Liste beim nächsten
|
||||
# Durchlauf sofort wieder ins Archiv.
|
||||
member.last_opened_at = utcnow()
|
||||
|
||||
# Beim Eigentümer zusätzlich die Vorwarnungen zurücknehmen: Die Frist
|
||||
# beginnt von vorn, also soll auch wieder gewarnt werden.
|
||||
if lst.owner_id == user.id:
|
||||
lst.warn_7d_at = None
|
||||
lst.warn_1d_at = None
|
||||
|
||||
db.commit()
|
||||
return _to_out(db, lst, member.role, member.may_share_public, None)
|
||||
|
||||
|
||||
@router.get("/{list_id}/snapshot", response_model=ListSnapshot)
|
||||
@@ -91,7 +173,7 @@ def snapshot(lst: ReadableList, db: DbSession, user: VerifiedUser):
|
||||
).all()
|
||||
|
||||
return ListSnapshot(
|
||||
list=_to_out(db, lst, member.role, member.may_share_public),
|
||||
list=_to_out(db, lst, member.role, member.may_share_public, member.archived_at),
|
||||
view=build_view(db, lst),
|
||||
markets=[MarketOut.model_validate(m) for m in markets],
|
||||
categories=[CategoryOut.model_validate(c) for c in categories],
|
||||
|
||||
Reference in New Issue
Block a user