perf(prompts): make /tags fetch only the tags column with SQL access filter (#24287)
Non-admin GET /api/v1/prompts/tags went through get_prompts_by_user_id, which loaded every active prompt with its full content/data/meta plus owner records and all access grants, then ran one has_access query per prompt that wasn't owned by the caller - all so the endpoint could collapse the result to a sorted tag list. With 600 prompts this took several seconds while the admin path (a single SELECT) returned in <1s. Add Prompts.get_tags_by_user_id which selects only the tags column and applies the same EXISTS-based access filter used by /list. Also tighten the admin get_tags to project just the tags column instead of full rows. The endpoint is now one DB query (plus one for groups), no row hydration, no N+1. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
26b1a3d7dc
commit
1a3e5ef4c1
2 files changed
+32
-12
No files matched your search
@@ -663,12 +663,38 @@ class PromptsTable:
|
|||||||
async def get_tags(self, db: Optional[AsyncSession] = None) -> list[str]:
|
async def get_tags(self, db: Optional[AsyncSession] = None) -> list[str]:
|
||||||
try:
|
try:
|
||||||
async with get_async_db_context(db) as db:
|
async with get_async_db_context(db) as db:
|
||||||
result = await db.execute(select(Prompt).filter_by(is_active=True))
|
result = await db.execute(select(Prompt.tags).filter(Prompt.is_active == True))
|
||||||
prompts = result.scalars().all()
|
|
||||||
tags = set()
|
tags = set()
|
||||||
for prompt in prompts:
|
for (tag_list,) in result.all():
|
||||||
if prompt.tags:
|
if tag_list:
|
||||||
for tag in prompt.tags:
|
for tag in tag_list:
|
||||||
|
if tag:
|
||||||
|
tags.add(tag)
|
||||||
|
return sorted(list(tags))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def get_tags_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> list[str]:
|
||||||
|
try:
|
||||||
|
async with get_async_db_context(db) as db:
|
||||||
|
user_groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||||
|
user_group_ids = [group.id for group in user_groups]
|
||||||
|
|
||||||
|
query = select(Prompt.tags).filter(Prompt.is_active == True)
|
||||||
|
query = AccessGrants.has_permission_filter(
|
||||||
|
db=db,
|
||||||
|
query=query,
|
||||||
|
DocumentModel=Prompt,
|
||||||
|
filter={'user_id': user_id, 'group_ids': user_group_ids},
|
||||||
|
resource_type='prompt',
|
||||||
|
permission='read',
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await db.execute(query)
|
||||||
|
tags = set()
|
||||||
|
for (tag_list,) in result.all():
|
||||||
|
if tag_list:
|
||||||
|
for tag in tag_list:
|
||||||
if tag:
|
if tag:
|
||||||
tags.add(tag)
|
tags.add(tag)
|
||||||
return sorted(list(tags))
|
return sorted(list(tags))
|
||||||
|
|||||||
@@ -61,13 +61,7 @@ async def get_prompts(user=Depends(get_verified_user), db: AsyncSession = Depend
|
|||||||
async def get_prompt_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
async def get_prompt_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||||
return await Prompts.get_tags(db=db)
|
return await Prompts.get_tags(db=db)
|
||||||
else:
|
return await Prompts.get_tags_by_user_id(user.id, db=db)
|
||||||
prompts = await Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
|
|
||||||
tags = set()
|
|
||||||
for prompt in prompts:
|
|
||||||
if prompt.tags:
|
|
||||||
tags.update(prompt.tags)
|
|
||||||
return sorted(list(tags))
|
|
||||||
|
|
||||||
|
|
||||||
@router.get('/list', response_model=PromptAccessListResponse)
|
@router.get('/list', response_model=PromptAccessListResponse)
|
||||||
|
|||||||
Reference in new issue
Block a user