feat: 实现日历提醒完整功能(操作执行、通知服务重构、归档)
- 新增 ReminderActionExecutor 处理取消/稍后提醒操作 - 新增 ReminderOutboxStore 本地存储待处理操作 - 重构 LocalNotificationService 支持聚合提醒和交互操作 - 新增 event_color_resolver 工具类统一颜色解析 - 新增 CalendarService.archiveEvent 归档方法 - 增强 ModelTracking 支持缓存命中、推理token和成本追踪 - 添加 qwen3.5-35b-a3b 模型配置 - 更新 AndroidManifest 全屏intent权限 - 补充相关单元测试和文档
This commit is contained in:
@@ -9,7 +9,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from core.db.base_repository import BaseRepository
|
||||
from core.logging import get_logger
|
||||
from models.schedule_items import ScheduleItem
|
||||
from models.schedule_items import ScheduleItem, ScheduleItemStatus
|
||||
from models.schedule_subscriptions import ScheduleSubscription, SubscriptionStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -61,6 +61,11 @@ class ScheduleItemRepository(Protocol):
|
||||
start_at: datetime,
|
||||
end_at: datetime,
|
||||
) -> Sequence[tuple[ScheduleItem, ScheduleSubscription]]: ...
|
||||
async def archive_expired_subscribed_items(
|
||||
self,
|
||||
subscriber_id: UUID,
|
||||
now_at: datetime,
|
||||
) -> int: ...
|
||||
|
||||
|
||||
class SQLAlchemyScheduleItemRepository(BaseRepository[ScheduleItem]):
|
||||
@@ -149,8 +154,13 @@ class SQLAlchemyScheduleItemRepository(BaseRepository[ScheduleItem]):
|
||||
select(ScheduleItem)
|
||||
.where(ScheduleItem.owner_id == owner_id)
|
||||
.where(ScheduleItem.deleted_at.is_(None))
|
||||
.where(ScheduleItem.start_at >= start_at)
|
||||
.where(ScheduleItem.start_at <= end_at)
|
||||
.where(
|
||||
or_(
|
||||
ScheduleItem.end_at.is_(None),
|
||||
ScheduleItem.end_at >= start_at,
|
||||
)
|
||||
)
|
||||
.order_by(ScheduleItem.start_at.asc())
|
||||
)
|
||||
result = await self._session.execute(stmt)
|
||||
@@ -308,8 +318,13 @@ class SQLAlchemyScheduleItemRepository(BaseRepository[ScheduleItem]):
|
||||
.where(ScheduleSubscription.subscriber_id == subscriber_id)
|
||||
.where(ScheduleSubscription.status == SubscriptionStatus.ACTIVE)
|
||||
.where(ScheduleItem.deleted_at.is_(None))
|
||||
.where(ScheduleItem.start_at >= start_at)
|
||||
.where(ScheduleItem.start_at <= end_at)
|
||||
.where(
|
||||
or_(
|
||||
ScheduleItem.end_at.is_(None),
|
||||
ScheduleItem.end_at >= start_at,
|
||||
)
|
||||
)
|
||||
.order_by(ScheduleItem.start_at.asc())
|
||||
)
|
||||
result = await self._session.execute(stmt)
|
||||
@@ -317,3 +332,38 @@ class SQLAlchemyScheduleItemRepository(BaseRepository[ScheduleItem]):
|
||||
except SQLAlchemyError:
|
||||
logger.exception("Failed to list subscribed items")
|
||||
raise
|
||||
|
||||
async def archive_expired_subscribed_items(
|
||||
self,
|
||||
subscriber_id: UUID,
|
||||
now_at: datetime,
|
||||
) -> int:
|
||||
try:
|
||||
item_ids_subquery = (
|
||||
select(ScheduleItem.id)
|
||||
.join(
|
||||
ScheduleSubscription,
|
||||
ScheduleSubscription.item_id == ScheduleItem.id,
|
||||
)
|
||||
.where(ScheduleSubscription.subscriber_id == subscriber_id)
|
||||
.where(ScheduleSubscription.status == SubscriptionStatus.ACTIVE)
|
||||
.where(ScheduleItem.deleted_at.is_(None))
|
||||
.where(ScheduleItem.status == ScheduleItemStatus.ACTIVE)
|
||||
.where(ScheduleItem.end_at.is_not(None))
|
||||
.where(ScheduleItem.end_at <= now_at)
|
||||
)
|
||||
|
||||
stmt = (
|
||||
update(ScheduleItem)
|
||||
.where(ScheduleItem.id.in_(item_ids_subquery))
|
||||
.values(status=ScheduleItemStatus.ARCHIVED)
|
||||
)
|
||||
result = await self._session.execute(stmt)
|
||||
await self._session.flush()
|
||||
return int(getattr(result, "rowcount", 0) or 0)
|
||||
except SQLAlchemyError:
|
||||
logger.exception(
|
||||
"Failed to archive expired subscribed items",
|
||||
subscriber_id=str(subscriber_id),
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -240,6 +240,11 @@ class ScheduleItemService(BaseService):
|
||||
raise HTTPException(status_code=400, detail="end_at must be after start_at")
|
||||
|
||||
try:
|
||||
archived_count = await self._repository.archive_expired_subscribed_items(
|
||||
user_id,
|
||||
datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
subscribed_items = (
|
||||
await self._repository.list_subscribed_items_by_date_range(
|
||||
user_id, normalized_start_at, normalized_end_at
|
||||
@@ -256,9 +261,12 @@ class ScheduleItemService(BaseService):
|
||||
)
|
||||
|
||||
results.sort(key=lambda x: x.start_at)
|
||||
if archived_count > 0:
|
||||
await self._session.commit()
|
||||
|
||||
return results
|
||||
except SQLAlchemyError:
|
||||
await self._session.rollback()
|
||||
logger.exception("Failed to list schedule items")
|
||||
raise HTTPException(
|
||||
status_code=503, detail="Schedule item store unavailable"
|
||||
|
||||
Reference in New Issue
Block a user