Files
xiaozhi-esp32-server/main/manager-api-fastapi/app/services/correctword.py
T

134 lines
5.2 KiB
Python

from __future__ import annotations
import uuid
from typing import Any
from app.core.errors import AppError
from app.core.security import AuthUser, shanghai_now_naive
from app.repositories.correctword import CorrectWordRepository
from app.schemas.correctword import CorrectWordFileBody
def _parse_lines(lines: list[str]) -> list[tuple[str, str]]:
result: list[tuple[str, str]] = []
for raw in lines:
line = raw.strip()
if not line or "|" not in line:
continue
source, target = line.split("|", 1)
if source.strip() and target.strip():
result.append((source.strip(), target.strip()))
return result
def _content_lines(value: str | None) -> list[str]:
if value is None:
return []
# Java String.split keeps one empty element for the empty source string,
# while still discarding trailing empty elements for non-empty strings.
if value == "":
return [""]
lines = value.split("\n")
while lines and lines[-1] == "":
lines.pop()
return lines
def file_vo(row: dict[str, Any]) -> dict[str, Any]:
return {
"id": row.get("id"),
"fileName": row.get("file_name"),
"wordCount": row.get("word_count"),
"content": _content_lines(row.get("content")),
"createdAt": row.get("created_at"),
"updatedAt": row.get("updated_at"),
}
class CorrectWordService:
def __init__(self, repository: CorrectWordRepository):
self.repository = repository
@staticmethod
def validate(body: CorrectWordFileBody, *, check_size: bool) -> None:
if body.file_name is None or not body.file_name.strip():
raise AppError(10034, "文件名不能为空")
if not body.content:
raise AppError(10034, "替换词内容不能为空")
if check_size and body.file_size is not None and body.file_size > 1024 * 1024:
raise AppError(10204)
async def create(self, body: CorrectWordFileBody, user: AuthUser) -> dict[str, Any]:
self.validate(body, check_size=True)
assert body.file_name is not None
assert body.content is not None
items = _parse_lines(body.content)
file_id, now = uuid.uuid4().hex, shanghai_now_naive()
values = {
"id": file_id,
"file_name": body.file_name,
"word_count": len(items),
"content": "\n".join(body.content),
"creator": user.id,
"now": now,
}
async with self.repository.session.begin():
if await self.repository.name_exists(user.id, body.file_name):
raise AppError(10203)
await self.repository.insert_file(values)
await self.repository.insert_items(
[
{"id": uuid.uuid4().hex, "file_id": file_id, "source_word": source, "target_word": target}
for source, target in items
]
)
return file_vo({**values, "created_at": now, "updated_at": None})
async def update(self, file_id: str, body: CorrectWordFileBody, user: AuthUser) -> None:
self.validate(body, check_size=False)
assert body.file_name is not None
assert body.content is not None
items = _parse_lines(body.content)
async with self.repository.session.begin():
row = await self.repository.get_file(file_id, for_update=True)
if row is None:
return
if await self.repository.name_exists(user.id, body.file_name, file_id):
raise AppError(500, f"文件名已存在:{body.file_name}")
await self.repository.delete_items(file_id)
await self.repository.insert_items(
[
{"id": uuid.uuid4().hex, "file_id": file_id, "source_word": source, "target_word": target}
for source, target in items
]
)
await self.repository.update_file(
{
"id": file_id,
"file_name": body.file_name,
"word_count": len(items),
"content": "\n".join(body.content),
"updater": user.id,
"now": shanghai_now_naive(),
}
)
async def page(self, user: AuthUser, page: str | None, limit: str | None) -> dict[str, Any]:
current, size = max(int(page or "1"), 1), int(limit or "10")
rows, total = await self.repository.list_files(user.id, offset=(current - 1) * size, limit=size)
return {"total": total, "list": [file_vo(row) for row in rows]}
async def all(self, user: AuthUser) -> list[dict[str, Any]]:
rows, _ = await self.repository.list_files(user.id)
return [file_vo(row) for row in rows]
async def get(self, file_id: str) -> dict[str, Any] | None:
row = await self.repository.get_file(file_id)
return file_vo(row) if row else None
async def delete(self, file_ids: list[str]) -> None:
async with self.repository.session.begin():
for file_id in file_ids:
if file_id and file_id.strip():
await self.repository.delete_file_graph(file_id.strip())