feat: 添加日历批量操作与客户端时区感知功能,优化前端 UI 交互体验
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import TYPE_CHECKING, Protocol, Literal
|
||||
from uuid import UUID
|
||||
|
||||
@@ -83,15 +84,18 @@ class ScheduleItemService(BaseService):
|
||||
) -> ScheduleItemResponse:
|
||||
user_id = self.require_user_id()
|
||||
|
||||
if request.end_at and request.end_at <= request.start_at:
|
||||
normalized_start_at = self._to_utc_required(request.start_at)
|
||||
normalized_end_at = self._to_utc(request.end_at)
|
||||
|
||||
if normalized_end_at and normalized_end_at <= normalized_start_at:
|
||||
raise HTTPException(status_code=400, detail="end_at must be after start_at")
|
||||
|
||||
data = {
|
||||
"owner_id": user_id,
|
||||
"title": request.title,
|
||||
"description": request.description,
|
||||
"start_at": request.start_at,
|
||||
"end_at": request.end_at,
|
||||
"start_at": normalized_start_at,
|
||||
"end_at": normalized_end_at,
|
||||
"timezone": request.timezone,
|
||||
"extra_metadata": request.metadata.model_dump() if request.metadata else {},
|
||||
"source_type": source_type,
|
||||
@@ -168,10 +172,21 @@ class ScheduleItemService(BaseService):
|
||||
# Validate time range
|
||||
next_start = update_data.get("start_at", existing.start_at)
|
||||
next_end = update_data.get("end_at", existing.end_at)
|
||||
if next_end is not None and next_end <= next_start:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="end_at must be after start_at"
|
||||
)
|
||||
if isinstance(next_start, datetime):
|
||||
next_start = self._to_utc_required(next_start)
|
||||
update_data["start_at"] = next_start
|
||||
if isinstance(next_end, datetime):
|
||||
next_end = self._to_utc(next_end)
|
||||
update_data["end_at"] = next_end
|
||||
if next_end is not None:
|
||||
if not isinstance(next_start, datetime):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="start_at must include timezone"
|
||||
)
|
||||
if next_end <= next_start:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="end_at must be after start_at"
|
||||
)
|
||||
|
||||
if not update_data:
|
||||
return self._to_response(existing)
|
||||
@@ -218,13 +233,16 @@ class ScheduleItemService(BaseService):
|
||||
) -> list[ScheduleItemResponse]:
|
||||
user_id = self.require_user_id()
|
||||
|
||||
if request.end_at <= request.start_at:
|
||||
normalized_start_at = self._to_utc_required(request.start_at)
|
||||
normalized_end_at = self._to_utc_required(request.end_at)
|
||||
|
||||
if normalized_end_at <= normalized_start_at:
|
||||
raise HTTPException(status_code=400, detail="end_at must be after start_at")
|
||||
|
||||
try:
|
||||
subscribed_items = (
|
||||
await self._repository.list_subscribed_items_by_date_range(
|
||||
user_id, request.start_at, request.end_at
|
||||
user_id, normalized_start_at, normalized_end_at
|
||||
)
|
||||
)
|
||||
|
||||
@@ -518,3 +536,18 @@ class ScheduleItemService(BaseService):
|
||||
|
||||
if subscriptions:
|
||||
await self._session.commit()
|
||||
|
||||
def _to_utc(self, dt: datetime | None) -> datetime | None:
|
||||
if dt is None:
|
||||
return None
|
||||
if dt.tzinfo is None:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="datetime must include timezone"
|
||||
)
|
||||
return dt.astimezone(timezone.utc)
|
||||
|
||||
def _to_utc_required(self, dt: datetime) -> datetime:
|
||||
normalized = self._to_utc(dt)
|
||||
if normalized is None:
|
||||
raise HTTPException(status_code=400, detail="datetime is required")
|
||||
return normalized
|
||||
|
||||
Reference in New Issue
Block a user