44from typing import Optional
55from functools import lru_cache
66
7- from sqlalchemy import select , delete , update , or_ , func , cast
7+ from sqlalchemy import Boolean , select , delete , update , or_ , func , cast
88from sqlalchemy .ext .asyncio import AsyncSession
99from open_webui .internal .db import Base , get_async_db_context
1010from open_webui .models .groups import Groups
@@ -29,6 +29,7 @@ class Note(Base):
2929 title = Column (Text )
3030 data = Column (JSON , nullable = True )
3131 meta = Column (JSON , nullable = True )
32+ is_pinned = Column (Boolean , default = False , nullable = True )
3233
3334 created_at = Column (BigInteger )
3435 updated_at = Column (BigInteger )
@@ -43,6 +44,7 @@ class NoteModel(BaseModel):
4344 title : str
4445 data : Optional [dict ] = None
4546 meta : Optional [dict ] = None
47+ is_pinned : Optional [bool ] = False
4648
4749 access_grants : list [AccessGrantModel ] = Field (default_factory = list )
4850
@@ -77,6 +79,7 @@ class NoteItemResponse(BaseModel):
7779 id : str
7880 title : str
7981 data : Optional [dict ]
82+ is_pinned : Optional [bool ] = False
8083 updated_at : int
8184 created_at : int
8285 user : Optional [UserResponse ] = None
@@ -311,6 +314,39 @@ async def update_note_by_id(
311314 await db .commit ()
312315 return await self ._to_note_model (note , db = db ) if note else None
313316
317+ async def toggle_note_pinned_by_id (self , id : str , db : Optional [AsyncSession ] = None ) -> Optional [NoteModel ]:
318+ try :
319+ async with get_async_db_context (db ) as db :
320+ result = await db .execute (select (Note ).filter (Note .id == id ))
321+ note = result .scalars ().first ()
322+ if not note :
323+ return None
324+ note .is_pinned = not note .is_pinned
325+ note .updated_at = int (time .time_ns ())
326+ await db .commit ()
327+ return await self ._to_note_model (note , db = db )
328+ except Exception :
329+ return None
330+
331+ async def get_pinned_notes_by_user_id (
332+ self ,
333+ user_id : str ,
334+ permission : str = 'read' ,
335+ db : Optional [AsyncSession ] = None ,
336+ ) -> list [NoteModel ]:
337+ async with get_async_db_context (db ) as db :
338+ user_groups = await Groups .get_groups_by_member_id (user_id , db = db )
339+ user_group_ids = [group .id for group in user_groups ]
340+
341+ stmt = select (Note ).filter (Note .is_pinned == True ).order_by (Note .updated_at .desc ())
342+ stmt = self ._has_permission (db , stmt , {'user_id' : user_id , 'group_ids' : user_group_ids }, permission )
343+
344+ result = await db .execute (stmt )
345+ notes = result .scalars ().all ()
346+ note_ids = [note .id for note in notes ]
347+ grants_map = await AccessGrants .get_grants_by_resources ('note' , note_ids , db = db )
348+ return [await self ._to_note_model (note , access_grants = grants_map .get (note .id , []), db = db ) for note in notes ]
349+
314350 async def delete_note_by_id (self , id : str , db : Optional [AsyncSession ] = None ) -> bool :
315351 try :
316352 async with get_async_db_context (db ) as db :
0 commit comments