mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-28 19:47:41 +08:00
refactor: 推进后端分层架构治理
This commit is contained in:
@@ -4,7 +4,7 @@ from typing import Any, Optional, Union
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.agentchat import AgentChat
|
||||
from app.schemas.types import NotificationChannel
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.agenttask import AgentTask
|
||||
from app.db.models.agenttaskrun import AgentTaskRun
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.downloadfailure import DownloadFailure
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from typing import Dict, List, Optional, cast
|
||||
|
||||
from app.db import DbOper
|
||||
from sqlalchemy import delete as sqlalchemy_delete, update as sqlalchemy_update
|
||||
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.downloadhistory import DownloadHistory, DownloadFiles
|
||||
from app.schemas.types import MediaSource
|
||||
|
||||
@@ -110,6 +112,17 @@ class DownloadHistoryOper(DbOper):
|
||||
"""
|
||||
DownloadFiles.delete_by_fullpath(self._db, fullpath)
|
||||
|
||||
def stage_delete_file_by_fullpath(self, fullpath: str) -> None:
|
||||
"""暂存指定完整路径的下载文件记录删除。"""
|
||||
self._db.execute(
|
||||
sqlalchemy_update(DownloadFiles)
|
||||
.where(
|
||||
DownloadFiles.fullpath == fullpath,
|
||||
DownloadFiles.state == 1,
|
||||
)
|
||||
.values(state=0)
|
||||
)
|
||||
|
||||
def get_hash_by_fullpath(self, fullpath: str) -> Optional[str]:
|
||||
"""
|
||||
按fullpath查询下载文件记录hash
|
||||
@@ -192,6 +205,14 @@ class DownloadHistoryOper(DbOper):
|
||||
"""
|
||||
DownloadHistory.delete(self._db, historyid)
|
||||
|
||||
def stage_delete_history(self, historyid: int) -> None:
|
||||
"""暂存下载记录删除,不由模型装饰器提交事务。"""
|
||||
self._db.execute(
|
||||
sqlalchemy_delete(DownloadHistory).where(
|
||||
DownloadHistory.id == historyid
|
||||
)
|
||||
)
|
||||
|
||||
def delete_downloadfile(self, downloadfileid):
|
||||
"""
|
||||
删除下载文件记录
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.mediaserver import MediaServerItem
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ from typing import Optional, Union
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.message import Message
|
||||
from app.schemas import NotificationChannel, MessageType
|
||||
from app.schemas.notification import NotificationChannel
|
||||
from app.schemas.message import MessageType
|
||||
|
||||
|
||||
class MessageOper(DbOper):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.plugindata import PluginData
|
||||
|
||||
|
||||
|
||||
+47
-3
@@ -1,9 +1,11 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Tuple, Optional
|
||||
from typing import Any, List, Mapping, Tuple, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.models import SiteIcon
|
||||
from sqlalchemy import delete as sqlalchemy_delete
|
||||
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.site import Site
|
||||
from app.db.models.siteicon import SiteIcon
|
||||
from app.db.models.sitestatistic import SiteStatistic
|
||||
from app.db.models.siteuserdata import SiteUserData
|
||||
|
||||
@@ -35,6 +37,48 @@ class SiteOper(DbOper):
|
||||
"""
|
||||
return await Site.async_get(self._db, sid)
|
||||
|
||||
async def get_by_id(self, site_id: int) -> Optional[Site]:
|
||||
"""读取站点写用例需要的目标站点。"""
|
||||
return await self.async_get(site_id)
|
||||
|
||||
async def get_by_domain(self, domain: str) -> Optional[Site]:
|
||||
"""按域名读取站点写用例的重复目标。"""
|
||||
return await Site.async_get_by_domain(self._db, domain)
|
||||
|
||||
async def stage_create(self, payload: Mapping[str, Any]) -> None:
|
||||
"""暂存新增站点,不由仓储自行提交。"""
|
||||
values = dict(payload)
|
||||
values.pop("id", None)
|
||||
self._db.add(Site(**values))
|
||||
|
||||
async def stage_update(
|
||||
self,
|
||||
site_id: int,
|
||||
payload: Mapping[str, Any],
|
||||
) -> bool:
|
||||
"""暂存站点字段更新,不由模型装饰器提前提交。"""
|
||||
site = await self.async_get(site_id)
|
||||
if not site:
|
||||
return False
|
||||
for key, value in payload.items():
|
||||
if key != "id":
|
||||
setattr(site, key, value)
|
||||
return True
|
||||
|
||||
async def stage_delete(self, site_id: int) -> None:
|
||||
"""暂存站点删除,由请求级 UnitOfWork 统一提交。"""
|
||||
await self._db.execute(
|
||||
sqlalchemy_delete(Site).where(Site.id == site_id)
|
||||
)
|
||||
|
||||
async def stage_priorities(self, priorities: list[dict]) -> None:
|
||||
"""暂存批量优先级更新,避免逐行独立提交。"""
|
||||
for priority in priorities:
|
||||
site_id = priority.get("id")
|
||||
site = await self.async_get(site_id) if site_id else None
|
||||
if site:
|
||||
site.pri = priority.get("pri")
|
||||
|
||||
def list(self) -> List[Site]:
|
||||
"""
|
||||
获取站点列表
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
import time
|
||||
from typing import Any, Tuple, List, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from sqlalchemy import delete as sqlalchemy_delete
|
||||
|
||||
from app.application.subscription.delete import SubscribeDeletionCandidate
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.db.models.subscribehistory import SubscribeHistory
|
||||
from app.schemas.types import MediaSource
|
||||
@@ -154,6 +157,75 @@ class SubscribeOper(DbOper):
|
||||
"""
|
||||
return await Subscribe.async_get(self._db, rid=sid)
|
||||
|
||||
async def get_candidate(
|
||||
self,
|
||||
subscribe_id: int,
|
||||
) -> Optional[SubscribeDeletionCandidate]:
|
||||
"""读取订阅删除用例需要的权限字段与完整事件快照。"""
|
||||
subscribe = await self.async_get(subscribe_id)
|
||||
if not subscribe:
|
||||
return None
|
||||
values = subscribe.__dict__
|
||||
event_payload = {
|
||||
column.name: values.get(column.name)
|
||||
for column in subscribe.__table__.columns
|
||||
}
|
||||
return SubscribeDeletionCandidate(
|
||||
subscribe_id=subscribe_id,
|
||||
username=subscribe.username,
|
||||
event_payload=event_payload,
|
||||
)
|
||||
|
||||
async def list_candidates_by_identity(
|
||||
self,
|
||||
media_source: MediaSource,
|
||||
media_id: str,
|
||||
season: Optional[int],
|
||||
music_type: Optional[str],
|
||||
) -> List[SubscribeDeletionCandidate]:
|
||||
"""按媒体身份读取去重后的订阅删除快照。"""
|
||||
subscribes = await Subscribe.async_list_by_media_identity(
|
||||
self._db,
|
||||
media_source=media_source,
|
||||
media_id=media_id,
|
||||
music_type=music_type,
|
||||
)
|
||||
candidates = []
|
||||
seen_ids = set()
|
||||
for subscribe in subscribes or []:
|
||||
subscribe_music_type = getattr(subscribe, "music_type", None)
|
||||
if music_type and not (
|
||||
subscribe_music_type == music_type
|
||||
or (music_type == "recording" and subscribe_music_type is None)
|
||||
):
|
||||
continue
|
||||
if season is not None and subscribe.season != season:
|
||||
continue
|
||||
if not subscribe.id or subscribe.id in seen_ids:
|
||||
continue
|
||||
seen_ids.add(subscribe.id)
|
||||
values = subscribe.__dict__
|
||||
candidates.append(
|
||||
SubscribeDeletionCandidate(
|
||||
subscribe_id=subscribe.id,
|
||||
username=subscribe.username,
|
||||
event_payload={
|
||||
column.name: values.get(column.name)
|
||||
for column in subscribe.__table__.columns
|
||||
},
|
||||
)
|
||||
)
|
||||
return candidates
|
||||
|
||||
async def list_search_ids(self, username: str, state: str) -> List[int]:
|
||||
"""返回用户指定状态的订阅编号,不向应用用例暴露 ORM 列表。"""
|
||||
subscribes = await Subscribe.async_list_by_username(
|
||||
self._db,
|
||||
username,
|
||||
state=state,
|
||||
)
|
||||
return [subscribe.id for subscribe in subscribes if subscribe.id]
|
||||
|
||||
def get_by(
|
||||
self, type: str, media_source: MediaSource, media_id: str,
|
||||
season: Optional[str] = None,
|
||||
@@ -206,6 +278,12 @@ class SubscribeOper(DbOper):
|
||||
"""
|
||||
await Subscribe.async_delete(self._db, rid=sid)
|
||||
|
||||
async def stage_delete(self, sid: int) -> None:
|
||||
"""登记订阅删除但不提交,由 Application UnitOfWork 控制事务边界。"""
|
||||
await self._db.execute(
|
||||
sqlalchemy_delete(Subscribe).where(Subscribe.id == sid)
|
||||
)
|
||||
|
||||
async def async_update(self, sid: int, payload: dict) -> Optional[Subscribe]:
|
||||
"""
|
||||
异步更新订阅。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.subscribehistory import SubscribeHistory
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import copy
|
||||
import threading
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.systemconfig import SystemConfig
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.foundation.singleton import Singleton
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import time
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from sqlalchemy import delete as sqlalchemy_delete
|
||||
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.schemas.types import MediaSource
|
||||
|
||||
@@ -207,6 +209,18 @@ class TransferHistoryOper(DbOper):
|
||||
"""
|
||||
TransferHistory.delete(self._db, historyid)
|
||||
|
||||
def stage_delete(self, historyid: int) -> None:
|
||||
"""暂存整理记录删除,不由模型装饰器提交事务。"""
|
||||
self._db.execute(
|
||||
sqlalchemy_delete(TransferHistory).where(
|
||||
TransferHistory.id == historyid
|
||||
)
|
||||
)
|
||||
|
||||
def stage_truncate(self) -> None:
|
||||
"""暂存全部整理记录删除,由请求级事务统一提交。"""
|
||||
self._db.execute(sqlalchemy_delete(TransferHistory))
|
||||
|
||||
async def async_delete(self, historyid):
|
||||
"""
|
||||
异步删除转移记录。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.transferpending import TransferPending
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ runtime 兼容映射指向 SDK 薄门面;canonical 数据访问模块仍只依
|
||||
"""
|
||||
from typing import List, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.user import User
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Any, Union, Dict, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.userconfig import UserConfig
|
||||
from app.schemas.types import UserConfigKey
|
||||
from app.foundation.singleton import Singleton
|
||||
|
||||
+57
-2
@@ -1,6 +1,8 @@
|
||||
from typing import List, Tuple, Optional, Any, Coroutine, Sequence
|
||||
from typing import List, Mapping, Tuple, Optional, Any
|
||||
|
||||
from app.db import DbOper
|
||||
from sqlalchemy import delete as sqlalchemy_delete
|
||||
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.workflow import Workflow
|
||||
|
||||
|
||||
@@ -25,6 +27,34 @@ class WorkflowOper(DbOper):
|
||||
"""
|
||||
return Workflow.get(self._db, wid)
|
||||
|
||||
def stage_state(self, workflow_id: int, state: str) -> bool:
|
||||
"""暂存工作流状态变更,不由模型方法自行提交。"""
|
||||
workflow = self.get(workflow_id)
|
||||
if not workflow:
|
||||
return False
|
||||
workflow.state = state
|
||||
return True
|
||||
|
||||
def stage_update(
|
||||
self,
|
||||
workflow_id: int,
|
||||
payload: Mapping[str, Any],
|
||||
) -> Optional[Workflow]:
|
||||
"""暂存工作流字段更新并返回同一会话中的对象。"""
|
||||
workflow = self.get(workflow_id)
|
||||
if not workflow:
|
||||
return None
|
||||
for key, value in payload.items():
|
||||
if key != "id":
|
||||
setattr(workflow, key, value)
|
||||
return workflow
|
||||
|
||||
def stage_delete(self, workflow_id: int) -> None:
|
||||
"""暂存工作流删除,由请求级 UnitOfWork 统一提交。"""
|
||||
self._db.execute(
|
||||
sqlalchemy_delete(Workflow).where(Workflow.id == workflow_id)
|
||||
)
|
||||
|
||||
async def async_get(self, wid: int) -> Optional[Workflow]:
|
||||
"""
|
||||
异步查询单个工作流
|
||||
@@ -73,6 +103,31 @@ class WorkflowOper(DbOper):
|
||||
"""
|
||||
return await Workflow.async_get_by_name(self._db, name)
|
||||
|
||||
async def stage_create(self, payload: Mapping[str, Any]) -> Workflow:
|
||||
"""暂存新工作流,不在操作器内提交事务。"""
|
||||
workflow = Workflow(**dict(payload))
|
||||
self._db.add(workflow)
|
||||
await self._db.flush()
|
||||
return workflow
|
||||
|
||||
async def stage_reset(
|
||||
self,
|
||||
workflow_id: int,
|
||||
reset_count: bool = False,
|
||||
) -> Optional[Workflow]:
|
||||
"""暂存工作流重置字段,不触发模型装饰器的隐式提交。"""
|
||||
workflow = await self.async_get(workflow_id)
|
||||
if not workflow:
|
||||
return None
|
||||
workflow.state = "W"
|
||||
workflow.result = None
|
||||
workflow.current_action = None
|
||||
workflow.context = {}
|
||||
workflow.execution_state = {}
|
||||
if reset_count:
|
||||
workflow.run_count = 0
|
||||
return workflow
|
||||
|
||||
def start(self, wid: int) -> bool:
|
||||
"""
|
||||
启动
|
||||
|
||||
Reference in New Issue
Block a user