105 lines
3.6 KiB
Python
105 lines
3.6 KiB
Python
"""匿名接口,统一放在 /api/public 下,一眼能看出哪些不鉴权"""
|
|
|
|
from typing import Annotated, Any
|
|
|
|
from fastapi import APIRouter, Query, Response
|
|
from fastapi.responses import FileResponse
|
|
|
|
from app.api.screens import Service, thumbnail_url
|
|
from app.common.response import ApiResponse, success_response
|
|
from app.config import settings
|
|
from app.core.exceptions import BizException
|
|
from app.schemas.saved_screen import (
|
|
DemoScreen,
|
|
DemoScreenList,
|
|
PublicScreen,
|
|
PublishBrief,
|
|
)
|
|
from app.services.screens import LIVE_VARS_MAX_CHARS, public_page
|
|
|
|
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}")
|
|
async def get_public_screen(
|
|
share_id: str, service: Service
|
|
) -> ApiResponse[PublicScreen]:
|
|
"""线上版本:返回发布快照,不是草稿;已下线、已删除、不存在都是 404"""
|
|
summary, snapshot = await service.get_live(share_id)
|
|
return success_response(
|
|
PublicScreen(
|
|
name=summary.name,
|
|
page=public_page(snapshot.page),
|
|
version=snapshot.version,
|
|
published_at=snapshot.published_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.get("/screens/{share_id}/datasources/{datasource_id}")
|
|
async def fetch_public_datasource(
|
|
share_id: str,
|
|
datasource_id: str,
|
|
service: Service,
|
|
variables: Annotated[
|
|
str | None, Query(alias="vars", max_length=LIVE_VARS_MAX_CHARS)
|
|
] = None,
|
|
) -> ApiResponse[dict[str, Any] | list[Any]]:
|
|
"""线上取数:地址取自已发布快照里的数据源配置,前端只传 ID
|
|
|
|
vars 是 JSON 对象 {变量ID: 值},只传该数据源 params 里引用到的变量;服务端只代入 params
|
|
"""
|
|
return success_response(
|
|
await service.fetch_live_datasource(share_id, datasource_id, variables)
|
|
)
|
|
|
|
|
|
@router.get("/thumbnails/{thumbnail_id}")
|
|
async def get_thumbnail(thumbnail_id: str, service: Service) -> FileResponse:
|
|
opened = service.open_thumbnail(thumbnail_id)
|
|
if opened is None:
|
|
raise BizException(404, "封面不存在")
|
|
path, media_type = opened
|
|
# 每次上传都换新 ID,内容永远不变,可以放心长期缓存
|
|
return FileResponse(
|
|
path,
|
|
media_type=media_type,
|
|
headers={"Cache-Control": "public, max-age=31536000, immutable"},
|
|
)
|