perf(api,frontend): index hot queries, paginate chats, fix realtime

This commit is contained in:
hh
2026-08-06 01:52:41 +02:00
parent 9d767d2531
commit 525ce024bc
27 changed files with 831 additions and 268 deletions
+6 -2
View File
@@ -34,6 +34,7 @@ from api.routers import (
watches,
)
from dependencies.container import container
from utils.cache import DAY_HEADERS, IMMUTABLE_HEADERS, NO_STORE_HEADERS
from utils.env import env
if env.auth.token is None:
@@ -105,8 +106,11 @@ if _spa_dir.is_dir():
async def serve_spa(spa_path: str) -> FileResponse:
candidate = (_spa_dir / spa_path).resolve()
if spa_path and candidate.is_relative_to(_spa_dir) and candidate.is_file():
return FileResponse(candidate)
return FileResponse(_spa_index)
immutable = spa_path.startswith("_app/immutable/")
return FileResponse(
candidate, headers=IMMUTABLE_HEADERS if immutable else DAY_HEADERS
)
return FileResponse(_spa_index, headers=NO_STORE_HEADERS)
app.add_middleware(BearerAuthMiddleware, token=_token)
+6 -1
View File
@@ -65,11 +65,16 @@ class EventHub:
return
account_id = event.get("account_id")
chat_id = event.get("chat_id")
scoped = event.get("kind") == "presence"
targets = [
sub
for sub in self._subscribers
if sub.account_id == account_id
and (sub.chat_id is None or sub.chat_id == chat_id)
and (
sub.chat_id == chat_id
if scoped
else sub.chat_id is None or sub.chat_id == chat_id
)
]
if not targets:
return
+4 -1
View File
@@ -5,6 +5,7 @@ from dishka.integrations.fastapi import DishkaRoute, FromDishka
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import FileResponse
from utils.cache import IMMUTABLE_HEADERS, SHORT_HEADERS
from utils.jobs import enqueue
from utils.read.avatars import avatar_by_unique_id, avatar_history, current_avatar
from utils.read.models import AvatarHistoryView
@@ -52,5 +53,7 @@ async def serve_avatar(
)
raise HTTPException(status_code=409, detail="avatar not downloaded; fetching")
return FileResponse(
storage.url(avatar.storage_key), media_type=avatar.mime or "image/jpeg"
storage.url(avatar.storage_key),
media_type=avatar.mime or "image/jpeg",
headers=IMMUTABLE_HEADERS if unique_id is not None else SHORT_HEADERS,
)
+18 -1
View File
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Query
from pydantic import BaseModel
from utils.jobs import enqueue
from utils.policy import repository
from utils.read import chats
from utils.read.models import (
DEFAULT_LIMIT,
@@ -35,8 +36,24 @@ async def list_chats(
account_id: AccountId,
limit: Limit = DEFAULT_LIMIT,
offset: Offset = 0,
folder_id: Annotated[int | None, Query()] = None,
search: Annotated[str | None, Query()] = None,
) -> list[ChatListItem]:
return await chats.list_chats(pool, account_id, Page(limit=limit, offset=offset))
folder = (
await repository.get_folder(pool, account_id, folder_id)
if folder_id is not None
else None
)
return await chats.list_chats(
pool, account_id, Page(limit=limit, offset=offset), folder=folder, search=search
)
@router.get("/chats/{chat_id}")
async def get_chat(
pool: FromDishka[asyncpg.Pool], chat_id: int, account_id: AccountId
) -> ChatListItem | None:
return await chats.get_chat(pool, account_id, chat_id)
@router.get("/chats/{chat_id}/messages")
+3
View File
@@ -5,6 +5,7 @@ from dishka.integrations.fastapi import DishkaRoute, FromDishka
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import FileResponse
from utils.cache import DAY_HEADERS, IMMUTABLE_HEADERS
from utils.read.media import (
get_media,
get_media_version,
@@ -47,6 +48,7 @@ async def serve_media_version(
return FileResponse(
storage.url(version.storage_key),
media_type=version.mime or "application/octet-stream",
headers=IMMUTABLE_HEADERS,
)
@@ -80,4 +82,5 @@ async def serve_media(
return FileResponse(
storage.url(media.storage_key),
media_type=media.mime or "application/octet-stream",
headers=DAY_HEADERS,
)