ForcePilot/backend/package/yuxi/repositories/skill_repository.py
supreme0597 df08fea2b8 feat(skills): 支持远程安装搜索、仓库历史记录与批量删除
- 后端新增全局搜索 skills.sh API (POST /system/skills/remote/search)
- 后端新增批量删除接口 (POST /system/skills/delete-batch),使用行锁防并发
- skill_repository.get_by_slug 支持 for_update 参数
- 前端远程安装弹框重构:Tab 双视图(按仓库拉取/全局搜索)、分页列表
- 前端仓库输入框新增历史记录功能(localStorage 持久化),支持逐条/批量清空
- 前端批量管理模式:全选/反选/清空 + 二次确认批量删除
- 修复 dropdown overlay teleport 到 body 导致 scoped CSS 无法穿透的样式问题
- 补充批量删除及并发锁单元测试
2026-05-25 17:54:58 +08:00

119 lines
3.5 KiB
Python

from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.storage.postgres.models_business import Skill
from yuxi.utils.datetime_utils import utc_now_naive
class SkillRepository:
def __init__(self, db_session: AsyncSession):
self.db = db_session
async def list_all(self) -> list[Skill]:
result = await self.db.execute(select(Skill).order_by(Skill.updated_at.desc(), Skill.id.desc()))
return list(result.scalars().all())
async def get_by_slug(self, slug: str, *, for_update: bool = False) -> Skill | None:
stmt = select(Skill).where(Skill.slug == slug)
if for_update:
stmt = stmt.with_for_update()
result = await self.db.execute(stmt)
return result.scalar_one_or_none()
async def exists_slug(self, slug: str) -> bool:
return (await self.get_by_slug(slug)) is not None
async def create(
self,
*,
slug: str,
name: str,
description: str,
tool_dependencies: list[str] | None,
mcp_dependencies: list[str] | None,
skill_dependencies: list[str] | None,
dir_path: str,
version: str | None = None,
is_builtin: bool = False,
content_hash: str | None = None,
created_by: str | None,
) -> Skill:
now = utc_now_naive()
item = Skill(
slug=slug,
name=name,
description=description,
tool_dependencies=tool_dependencies or [],
mcp_dependencies=mcp_dependencies or [],
skill_dependencies=skill_dependencies or [],
dir_path=dir_path,
version=version,
is_builtin=is_builtin,
content_hash=content_hash,
created_by=created_by,
updated_by=created_by,
created_at=now,
updated_at=now,
)
self.db.add(item)
await self.db.commit()
await self.db.refresh(item)
return item
async def update_builtin_install(
self,
item: Skill,
*,
version: str,
content_hash: str,
updated_by: str | None,
) -> Skill:
item.version = version
item.content_hash = content_hash
item.is_builtin = True
item.updated_by = updated_by
item.updated_at = utc_now_naive()
await self.db.commit()
await self.db.refresh(item)
return item
async def update_dependencies(
self,
item: Skill,
*,
tool_dependencies: list[str],
mcp_dependencies: list[str],
skill_dependencies: list[str],
updated_by: str | None,
) -> Skill:
item.tool_dependencies = tool_dependencies
item.mcp_dependencies = mcp_dependencies
item.skill_dependencies = skill_dependencies
item.updated_by = updated_by
item.updated_at = utc_now_naive()
await self.db.commit()
await self.db.refresh(item)
return item
async def update_metadata(
self,
item: Skill,
*,
name: str,
description: str,
updated_by: str | None,
) -> Skill:
item.name = name
item.description = description
item.updated_by = updated_by
item.updated_at = utc_now_naive()
await self.db.commit()
await self.db.refresh(item)
return item
async def delete(self, item: Skill) -> None:
await self.db.delete(item)
await self.db.commit()