Merge pull request 'feat:新增演示' (#13) from feat/0.3.0 into main
my-v0-server / deploy (push) Successful in 49s
my-v0-server / deploy (push) Successful in 49s
Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
@@ -41,6 +41,8 @@ SCREEN_MAX_MB=10
|
|||||||
# 回收站里的大屏保留多少天,超过后自动彻底删除
|
# 回收站里的大屏保留多少天,超过后自动彻底删除
|
||||||
TRASH_RETENTION_DAYS=30
|
TRASH_RETENTION_DAYS=30
|
||||||
THUMBNAIL_DIR=data/thumbnails
|
THUMBNAIL_DIR=data/thumbnails
|
||||||
|
# 未登录演示:展示这个用户 ID 名下的大屏(用该账号登录后在编辑器里做即可);留空则没有演示数据
|
||||||
|
DEMO_OWNER_ID=
|
||||||
|
|
||||||
# 模型写的单条数据查询的超时(秒)
|
# 模型写的单条数据查询的超时(秒)
|
||||||
DATA_QUERY_TIMEOUT=10
|
DATA_QUERY_TIMEOUT=10
|
||||||
|
|||||||
+45
-3
@@ -2,17 +2,59 @@
|
|||||||
|
|
||||||
from typing import Annotated, Any
|
from typing import Annotated, Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Query
|
from fastapi import APIRouter, Query, Response
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
from app.api.screens import Service
|
from app.api.screens import Service, thumbnail_url
|
||||||
from app.common.response import ApiResponse, success_response
|
from app.common.response import ApiResponse, success_response
|
||||||
|
from app.config import settings
|
||||||
from app.core.exceptions import BizException
|
from app.core.exceptions import BizException
|
||||||
from app.schemas.saved_screen import PublicScreen
|
from app.schemas.saved_screen import (
|
||||||
|
DemoScreen,
|
||||||
|
DemoScreenList,
|
||||||
|
PublicScreen,
|
||||||
|
PublishBrief,
|
||||||
|
)
|
||||||
from app.services.screens import LIVE_VARS_MAX_CHARS, public_page
|
from app.services.screens import LIVE_VARS_MAX_CHARS, public_page
|
||||||
|
|
||||||
router = APIRouter(prefix="/public", tags=["Public"])
|
router = APIRouter(prefix="/public", tags=["Public"])
|
||||||
|
|
||||||
|
# 演示数据的条数上限和浏览器缓存时长(秒)
|
||||||
|
DEMO_LIMIT = 20
|
||||||
|
DEMO_CACHE_SECONDS = 60
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/demo/screens")
|
||||||
|
async def list_demo_screens(
|
||||||
|
service: Service, response: Response
|
||||||
|
) -> ApiResponse[DemoScreenList]:
|
||||||
|
"""未登录演示:演示账号(DEMO_OWNER_ID)下的大屏,页面去掉数据源 headers;没配账号时为空"""
|
||||||
|
owner_id = settings.demo_owner_id
|
||||||
|
if owner_id is None:
|
||||||
|
return success_response(DemoScreenList(items=[]))
|
||||||
|
records = await service.list_demo(owner_id, DEMO_LIMIT)
|
||||||
|
response.headers["Cache-Control"] = f"public, max-age={DEMO_CACHE_SECONDS}"
|
||||||
|
return success_response(
|
||||||
|
DemoScreenList(
|
||||||
|
items=[
|
||||||
|
DemoScreen(
|
||||||
|
screen_id=record.screen_id,
|
||||||
|
name=record.name,
|
||||||
|
page=public_page(record.page),
|
||||||
|
thumbnail_url=thumbnail_url(record.thumbnail),
|
||||||
|
revision=record.revision,
|
||||||
|
updated_at=record.updated_at,
|
||||||
|
publish=None
|
||||||
|
if record.publish is None
|
||||||
|
else PublishBrief.model_validate(
|
||||||
|
record.publish.model_dump(exclude={"revision"})
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for record in records
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/screens/{share_id}")
|
@router.get("/screens/{share_id}")
|
||||||
async def get_public_screen(
|
async def get_public_screen(
|
||||||
|
|||||||
@@ -133,6 +133,13 @@ class Settings:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 演示账号的用户 ID:未登录访问时展示这个账号下的大屏,不配置则没有演示数据
|
||||||
|
demo_owner_id: int | None = field(
|
||||||
|
default_factory=lambda: (
|
||||||
|
int(value) if (value := os.getenv("DEMO_OWNER_ID", "").strip()) else None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# 模型写的单条数据查询的超时(秒)
|
# 模型写的单条数据查询的超时(秒)
|
||||||
data_query_timeout: float = field(
|
data_query_timeout: float = field(
|
||||||
default_factory=lambda: float(os.getenv("DATA_QUERY_TIMEOUT", "10"))
|
default_factory=lambda: float(os.getenv("DATA_QUERY_TIMEOUT", "10"))
|
||||||
|
|||||||
@@ -109,6 +109,22 @@ class ScreenDetail(BaseModel):
|
|||||||
publish: PublishBrief | None
|
publish: PublishBrief | None
|
||||||
|
|
||||||
|
|
||||||
|
class DemoScreen(BaseModel):
|
||||||
|
"""未登录演示用的大屏:带页面,前端据此在内存里还原一份可随意修改的大屏列表"""
|
||||||
|
|
||||||
|
screen_id: str
|
||||||
|
name: str
|
||||||
|
page: dict[str, Any]
|
||||||
|
thumbnail_url: str | None
|
||||||
|
revision: int
|
||||||
|
updated_at: datetime
|
||||||
|
publish: PublishBrief | None
|
||||||
|
|
||||||
|
|
||||||
|
class DemoScreenList(BaseModel):
|
||||||
|
items: list[DemoScreen]
|
||||||
|
|
||||||
|
|
||||||
class ScreenSaved(BaseModel):
|
class ScreenSaved(BaseModel):
|
||||||
revision: int
|
revision: int
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|||||||
@@ -428,6 +428,21 @@ class ScreenService:
|
|||||||
deleted=deleted,
|
deleted=deleted,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def list_demo(self, owner_id: int, limit: int) -> list[ScreenRecord]:
|
||||||
|
"""演示账号下没删除的大屏(最近修改在前),带页面"""
|
||||||
|
summaries, _ = await self._store.search(
|
||||||
|
owner_id,
|
||||||
|
keyword=None,
|
||||||
|
status=None,
|
||||||
|
sort="updated_at",
|
||||||
|
descending=True,
|
||||||
|
limit=limit,
|
||||||
|
offset=0,
|
||||||
|
deleted=False,
|
||||||
|
)
|
||||||
|
records = [await self._store.get(item.screen_id, owner_id) for item in summaries]
|
||||||
|
return [record for record in records if record is not None]
|
||||||
|
|
||||||
async def copy_screen(self, screen_id: str, owner_id: int, name: str | None) -> str:
|
async def copy_screen(self, screen_id: str, owner_id: int, name: str | None) -> str:
|
||||||
"""只复制草稿和封面,不复制发布状态和历史"""
|
"""只复制草稿和封面,不复制发布状态和历史"""
|
||||||
source = await self.get(screen_id, owner_id)
|
source = await self.get(screen_id, owner_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user