diff --git a/api/routers.py b/api/routers.py index b7a0541..8e290b5 100644 --- a/api/routers.py +++ b/api/routers.py @@ -19,12 +19,14 @@ from domain.audit import api as audit from domain.permission import api as permission from domain.user import api as user from domain.role import api as role +from domain.recent_files import api as recent_files def include_routers(app: FastAPI): app.include_router(adapters.router) app.include_router(search_api.router) app.include_router(virtual_fs.router) + app.include_router(recent_files.router) app.include_router(auth.router) app.include_router(config.router) app.include_router(processors.router) diff --git a/domain/recent_files/__init__.py b/domain/recent_files/__init__.py new file mode 100644 index 0000000..502d465 --- /dev/null +++ b/domain/recent_files/__init__.py @@ -0,0 +1,3 @@ +from .api import router + +__all__ = ["router"] diff --git a/domain/recent_files/api.py b/domain/recent_files/api.py new file mode 100644 index 0000000..92aa600 --- /dev/null +++ b/domain/recent_files/api.py @@ -0,0 +1,44 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends, Query, Request + +from api.response import success +from domain.audit import AuditAction, audit +from domain.auth import User, get_current_active_user + +from .service import RecentFilesService +from .types import RecordRecentFileRequest + +router = APIRouter(prefix="/api/fs/recent", tags=["recent-files"]) + + +@router.get("/") +@audit(action=AuditAction.READ, description="查看最近打开文件") +async def list_recent_files( + request: Request, + current_user: Annotated[User, Depends(get_current_active_user)], + limit: int = Query(20, ge=1, le=200, description="返回数量"), +): + data = await RecentFilesService.list_recent_files(current_user.id, limit) + return success(data) + + +@router.post("/") +@audit(action=AuditAction.CREATE, description="记录最近打开文件", body_fields=["path"]) +async def record_recent_file( + request: Request, + body: RecordRecentFileRequest, + current_user: Annotated[User, Depends(get_current_active_user)], +): + data = await RecentFilesService.record_opened_file(current_user.id, body.path) + return success(data) + + +@router.delete("/") +@audit(action=AuditAction.DELETE, description="清空最近打开文件") +async def clear_recent_files( + request: Request, + current_user: Annotated[User, Depends(get_current_active_user)], +): + data = await RecentFilesService.clear_recent_files(current_user.id) + return success(data) diff --git a/domain/recent_files/service.py b/domain/recent_files/service.py new file mode 100644 index 0000000..a1dd0fe --- /dev/null +++ b/domain/recent_files/service.py @@ -0,0 +1,23 @@ +from datetime import datetime, timezone + +from models.database import RecentFile + + +class RecentFilesService: + @staticmethod + async def record_opened_file(user_id: int, path: str) -> dict: + item, created = await RecentFile.get_or_create(user_id=user_id, path=path) + if not created: + await RecentFile.filter(id=item.id).update(opened_at=datetime.now(timezone.utc)) + await item.fetch_from_db() + return {"id": item.id, "path": item.path, "opened_at": item.opened_at.isoformat()} + + @staticmethod + async def list_recent_files(user_id: int, limit: int) -> list[dict]: + items = await RecentFile.filter(user_id=user_id).order_by("-opened_at").limit(limit) + return [{"id": i.id, "path": i.path, "opened_at": i.opened_at.isoformat()} for i in items] + + @staticmethod + async def clear_recent_files(user_id: int) -> dict: + deleted = await RecentFile.filter(user_id=user_id).delete() + return {"deleted": deleted} diff --git a/domain/recent_files/types.py b/domain/recent_files/types.py new file mode 100644 index 0000000..a05857e --- /dev/null +++ b/domain/recent_files/types.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel, Field + + +class RecordRecentFileRequest(BaseModel): + path: str = Field(..., min_length=1, max_length=4096, description="文件完整路径") + + +class RecentFileItem(BaseModel): + id: int + path: str + opened_at: str diff --git a/models/database.py b/models/database.py index bf6d75e..e03e92b 100644 --- a/models/database.py +++ b/models/database.py @@ -234,6 +234,19 @@ class ShareLink(Model): table = "share_links" +class RecentFile(Model): + id = fields.IntField(pk=True) + user: fields.ForeignKeyRelation[UserAccount] = fields.ForeignKeyField( + "models.UserAccount", related_name="recent_files", on_delete=fields.CASCADE + ) + path = fields.CharField(max_length=4096) + opened_at = fields.DatetimeField(auto_now=True) + + class Meta: + table = "recent_files" + unique_together = (("user", "path"),) + + class Plugin(Model): id = fields.IntField(pk=True) key = fields.CharField(max_length=100, unique=True) # 插件唯一标识