feat(backend): notes list across chats with the message and chat they belong to, filter by tag, tag counts
This commit is contained in:
1 parent
97f9082f5e
commit
a0e134756c
2 files changed
+93
-2
No files matched your search
@@ -6,7 +6,7 @@ from fastapi import APIRouter, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from utils.read import annotations
|
||||
from utils.read.models import DEFAULT_LIMIT, AnnotationView, Page
|
||||
from utils.read.models import DEFAULT_LIMIT, AnnotationView, NoteTag, NoteView, Page
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/api/annotations", tags=["annotations"], route_class=DishkaRoute
|
||||
@@ -42,6 +42,26 @@ async def list_annotations(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/notes")
|
||||
async def list_notes(
|
||||
pool: FromDishka[asyncpg.Pool],
|
||||
account_id: Annotated[int, Query()],
|
||||
tag: Annotated[str | None, Query()] = None,
|
||||
limit: Annotated[int, Query()] = DEFAULT_LIMIT,
|
||||
offset: Annotated[int, Query()] = 0,
|
||||
) -> list[NoteView]:
|
||||
return await annotations.list_notes(
|
||||
pool, account_id, Page(limit=limit, offset=offset), tag=tag
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tags")
|
||||
async def list_tags(
|
||||
pool: FromDishka[asyncpg.Pool], account_id: Annotated[int, Query()]
|
||||
) -> list[NoteTag]:
|
||||
return await annotations.list_tags(pool, account_id)
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
async def create_annotation(
|
||||
pool: FromDishka[asyncpg.Pool], body: AnnotationCreate
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
import asyncpg
|
||||
|
||||
from utils.read.models import AnnotationView, Page
|
||||
from utils.read.chats import get_chats
|
||||
from utils.read.models import AnnotationView, NoteTag, NoteView, Page
|
||||
|
||||
_COLS = "id, account_id, chat_id, message_id, text, created_at, updated_at"
|
||||
|
||||
TAG = re.compile(r"(?<!\w)#(\w+)")
|
||||
WORD = re.compile(r"\w+")
|
||||
|
||||
_NOTES = """
|
||||
SELECT a.id, a.account_id, a.chat_id, a.message_id, a.text,
|
||||
a.created_at, a.updated_at,
|
||||
m.text AS message_text, m.date AS message_date,
|
||||
COALESCE(m.has_media, false) AS message_has_media,
|
||||
m.deleted_at IS NOT NULL AS message_deleted
|
||||
FROM annotations a
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT text, date, has_media, deleted_at FROM messages
|
||||
WHERE account_id = a.account_id AND chat_id = a.chat_id
|
||||
AND message_id = a.message_id
|
||||
LIMIT 1
|
||||
) m ON true
|
||||
WHERE a.account_id = $1 AND ($4::text IS NULL OR a.text ~* $4)
|
||||
ORDER BY a.created_at DESC, a.id DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
"""
|
||||
|
||||
|
||||
async def list_annotations(
|
||||
pool: asyncpg.Pool,
|
||||
@@ -70,3 +95,49 @@ async def update_annotation(
|
||||
async def delete_annotation(pool: asyncpg.Pool, annotation_id: int) -> bool:
|
||||
result = await pool.execute("DELETE FROM annotations WHERE id = $1", annotation_id)
|
||||
return result.endswith("1")
|
||||
|
||||
|
||||
def _tag_pattern(tag: str | None) -> str | None:
|
||||
if tag is None:
|
||||
return None
|
||||
return f"(^|[^[:alnum:]_])#{tag}($|[^[:alnum:]_])"
|
||||
|
||||
|
||||
async def list_notes(
|
||||
pool: asyncpg.Pool, account_id: int, page: Page, *, tag: str | None = None
|
||||
) -> list[NoteView]:
|
||||
if tag is not None and not WORD.fullmatch(tag):
|
||||
return []
|
||||
rows = await pool.fetch(
|
||||
_NOTES, account_id, page.capped_limit, page.offset, _tag_pattern(tag)
|
||||
)
|
||||
chats = {
|
||||
chat.chat_id: chat
|
||||
for chat in await get_chats(
|
||||
pool, account_id, list({row["chat_id"] for row in rows})
|
||||
)
|
||||
}
|
||||
notes: list[NoteView] = []
|
||||
for row in rows:
|
||||
chat = chats.get(row["chat_id"])
|
||||
notes.append(
|
||||
NoteView(
|
||||
**dict(row),
|
||||
chat_title=chat.title if chat else None,
|
||||
chat_has_avatar=chat.has_avatar if chat else False,
|
||||
)
|
||||
)
|
||||
return notes
|
||||
|
||||
|
||||
async def list_tags(pool: asyncpg.Pool, account_id: int) -> list[NoteTag]:
|
||||
rows = await pool.fetch(
|
||||
"SELECT text FROM annotations WHERE account_id = $1", account_id
|
||||
)
|
||||
counts: Counter[str] = Counter()
|
||||
for row in rows:
|
||||
counts.update({tag.lower() for tag in TAG.findall(row["text"])})
|
||||
return [
|
||||
NoteTag(tag=tag, count=count)
|
||||
for tag, count in sorted(counts.items(), key=lambda item: (-item[1], item[0]))
|
||||
]
|
||||
Reference in new issue
Block a user