test
This commit is contained in:
167
scripts/service/todo_service.py
Normal file
167
scripts/service/todo_service.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""待办事项领域服务(业务内核)。
|
||||
|
||||
按模板铁律:「单业务内核、多入口」——CLI / Action / 数据管理按钮 / 定时任务
|
||||
都调用这里的同一个函数,禁止按入口分叉业务行为。
|
||||
|
||||
直接用 sqlite3 做持久化(demo 简化,未抽 repository)。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from db.connection import get_conn
|
||||
|
||||
# 业务常量(不暴露给宿主,技能自定)
|
||||
STATUS_PENDING = "pending"
|
||||
STATUS_DONE = "done"
|
||||
VALID_STATUS = {STATUS_PENDING, STATUS_DONE}
|
||||
|
||||
PRIORITY_HIGH = "high"
|
||||
PRIORITY_NORMAL = "normal"
|
||||
PRIORITY_LOW = "low"
|
||||
VALID_PRIORITY = {PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_LOW}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Todo:
|
||||
id: int
|
||||
title: str
|
||||
status: str
|
||||
priority: str
|
||||
due_at: Optional[int]
|
||||
created_at: int
|
||||
updated_at: int
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"title": self.title,
|
||||
"status": self.status,
|
||||
"priority": self.priority,
|
||||
"due_at": self.due_at,
|
||||
"created_at": self.created_at,
|
||||
"updated_at": self.updated_at,
|
||||
}
|
||||
|
||||
|
||||
def _now() -> int:
|
||||
return int(time.time())
|
||||
|
||||
|
||||
def add_todo(
|
||||
*,
|
||||
title: str,
|
||||
priority: str = PRIORITY_NORMAL,
|
||||
due_at: Optional[int] = None,
|
||||
) -> Todo:
|
||||
"""新增一条待办。领域校验:标题非空、优先级合法。"""
|
||||
title = (title or "").strip()
|
||||
if not title:
|
||||
raise ValueError("标题不能为空")
|
||||
if priority not in VALID_PRIORITY:
|
||||
raise ValueError(f"优先级不合法,可选:{sorted(VALID_PRIORITY)}")
|
||||
now = _now()
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO todos (title, status, priority, due_at, created_at, updated_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(title, STATUS_PENDING, priority, due_at, now, now),
|
||||
)
|
||||
new_id = cur.lastrowid
|
||||
conn.commit()
|
||||
return Todo(new_id, title, STATUS_PENDING, priority, due_at, now, now)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def list_todos(*, status: Optional[str] = None, limit: int = 100) -> list[Todo]:
|
||||
"""列出待办;可按状态过滤。默认按创建时间倒序。"""
|
||||
sql = "SELECT id, title, status, priority, due_at, created_at, updated_at FROM todos"
|
||||
params: list = []
|
||||
if status:
|
||||
if status not in VALID_STATUS:
|
||||
raise ValueError(f"状态不合法,可选:{sorted(VALID_STATUS)}")
|
||||
sql += " WHERE status = ?"
|
||||
params.append(status)
|
||||
sql += " ORDER BY created_at DESC LIMIT ?"
|
||||
params.append(limit)
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(sql, params)
|
||||
rows = cur.fetchall()
|
||||
return [Todo(*row) for row in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_todo(todo_id: int) -> Optional[Todo]:
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"SELECT id, title, status, priority, due_at, created_at, updated_at "
|
||||
"FROM todos WHERE id = ?",
|
||||
(todo_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return Todo(*row) if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def mark_done(todo_id: int) -> Todo:
|
||||
"""标记一条待办为完成。"""
|
||||
todo = get_todo(todo_id)
|
||||
if not todo:
|
||||
raise ValueError(f"待办 {todo_id} 不存在")
|
||||
if todo.status == STATUS_DONE:
|
||||
raise ValueError(f"待办 {todo_id} 已是完成状态")
|
||||
now = _now()
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"UPDATE todos SET status = ?, updated_at = ? WHERE id = ?",
|
||||
(STATUS_DONE, now, todo_id),
|
||||
)
|
||||
conn.commit()
|
||||
todo.status = STATUS_DONE
|
||||
todo.updated_at = now
|
||||
return todo
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def delete_todo(todo_id: int) -> bool:
|
||||
"""删除一条待办;返回是否真的删了一行。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("DELETE FROM todos WHERE id = ?", (todo_id,))
|
||||
conn.commit()
|
||||
return cur.rowcount > 0
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def stats() -> dict:
|
||||
"""简单统计:用于 ``todo stats`` 命令与 demo 报告。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT status, COUNT(*) FROM todos GROUP BY status")
|
||||
by_status = {row[0]: int(row[1]) for row in cur.fetchall()}
|
||||
cur.execute("SELECT COUNT(*) FROM todos")
|
||||
total = int(cur.fetchone()[0])
|
||||
return {
|
||||
"total": total,
|
||||
"pending": by_status.get(STATUS_PENDING, 0),
|
||||
"done": by_status.get(STATUS_DONE, 0),
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user