mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-29 17:54:39 +00:00
766 lines
24 KiB
Python
766 lines
24 KiB
Python
import json
|
|
import logging
|
|
from typing import Optional
|
|
from uuid import uuid4
|
|
|
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, status
|
|
from open_webui.config import (
|
|
BYPASS_ADMIN_ACCESS_CONTROL,
|
|
ENABLE_ADMIN_CHAT_ACCESS,
|
|
ENABLE_ADMIN_EXPORT,
|
|
)
|
|
from open_webui.constants import ERROR_MESSAGES
|
|
from open_webui.events import EVENTS, publish_event
|
|
from open_webui.internal.db import get_async_session
|
|
from open_webui.models.access_grants import AccessGrants
|
|
from open_webui.models.chats import ChatForm, ChatResponse, Chats
|
|
from open_webui.models.config import Config
|
|
from open_webui.models.groups import Groups
|
|
from open_webui.models.notes import (
|
|
NoteForm,
|
|
NoteListResponse,
|
|
NoteModel,
|
|
Notes,
|
|
NoteUserResponse,
|
|
)
|
|
from open_webui.models.users import UserResponse, Users
|
|
from open_webui.socket.main import sio
|
|
from open_webui.utils.access_control import (
|
|
filter_allowed_access_grants,
|
|
has_permission,
|
|
has_public_read_access_grant,
|
|
has_public_write_access_grant,
|
|
)
|
|
from open_webui.utils.auth import get_admin_user, get_verified_user
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _truncate_note_data(data: Optional[dict], max_length: int = 1000) -> Optional[dict]:
|
|
if not data:
|
|
return data
|
|
md = (data.get('content') or {}).get('md') or ''
|
|
return {'content': {'md': md[:max_length]}}
|
|
|
|
|
|
############################
|
|
# GetNotes
|
|
############################
|
|
|
|
|
|
class NoteItemResponse(BaseModel):
|
|
id: str
|
|
title: str
|
|
data: Optional[dict]
|
|
is_pinned: Optional[bool] = False
|
|
updated_at: int
|
|
created_at: int
|
|
user: Optional[UserResponse] = None
|
|
|
|
|
|
@router.get('/', response_model=list[NoteItemResponse])
|
|
async def get_notes(
|
|
request: Request,
|
|
page: Optional[int] = None,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
limit = None
|
|
skip = None
|
|
if page is not None:
|
|
limit = 60
|
|
skip = (page - 1) * limit
|
|
|
|
notes = await Notes.get_notes_by_user_id(user.id, 'read', skip=skip, limit=limit, db=db)
|
|
if not notes:
|
|
return []
|
|
|
|
user_ids = list(set(note.user_id for note in notes))
|
|
users = {user.id: user for user in await Users.get_users_by_user_ids(user_ids, db=db)}
|
|
|
|
pinned_note_ids = await Notes.get_pinned_note_ids(user.id, db=db)
|
|
|
|
return [
|
|
NoteUserResponse(
|
|
**{
|
|
**note.model_dump(),
|
|
'is_pinned': note.id in pinned_note_ids,
|
|
'data': _truncate_note_data(note.data),
|
|
'user': UserResponse(**users[note.user_id].model_dump()),
|
|
}
|
|
)
|
|
for note in notes
|
|
if note.user_id in users
|
|
]
|
|
|
|
|
|
############################
|
|
# GetPinnedNotes
|
|
############################
|
|
|
|
|
|
@router.get('/pinned', response_model=list[NoteItemResponse])
|
|
async def get_pinned_notes(
|
|
request: Request,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
notes = await Notes.get_pinned_notes_by_user_id(user.id, 'read', db=db)
|
|
if not notes:
|
|
return []
|
|
|
|
user_ids = list(set(note.user_id for note in notes))
|
|
users = {user.id: user for user in await Users.get_users_by_user_ids(user_ids, db=db)}
|
|
|
|
return [
|
|
NoteUserResponse(
|
|
**{
|
|
**note.model_dump(),
|
|
'is_pinned': True,
|
|
'data': _truncate_note_data(note.data),
|
|
'user': UserResponse(**users[note.user_id].model_dump()),
|
|
}
|
|
)
|
|
for note in notes
|
|
if note.user_id in users
|
|
]
|
|
|
|
|
|
@router.get('/search', response_model=NoteListResponse)
|
|
async def search_notes(
|
|
request: Request,
|
|
query: Optional[str] = None,
|
|
view_option: Optional[str] = None,
|
|
permission: Optional[str] = None,
|
|
order_by: Optional[str] = None,
|
|
direction: Optional[str] = None,
|
|
page: Optional[int] = 1,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
limit = None
|
|
skip = None
|
|
if page is not None:
|
|
limit = 60
|
|
skip = (page - 1) * limit
|
|
|
|
filter = {}
|
|
if query:
|
|
filter['query'] = query
|
|
if view_option:
|
|
filter['view_option'] = view_option
|
|
if permission:
|
|
filter['permission'] = permission
|
|
if order_by:
|
|
filter['order_by'] = order_by
|
|
if direction:
|
|
filter['direction'] = direction
|
|
|
|
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
|
|
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
|
if groups:
|
|
filter['group_ids'] = [group.id for group in groups]
|
|
|
|
filter['user_id'] = user.id
|
|
|
|
result = await Notes.search_notes(user.id, filter, skip=skip, limit=limit, db=db)
|
|
pinned_note_ids = await Notes.get_pinned_note_ids(user.id, db=db)
|
|
for note in result.items:
|
|
note.is_pinned = note.id in pinned_note_ids
|
|
note.data = _truncate_note_data(note.data)
|
|
return result
|
|
|
|
|
|
############################
|
|
# CreateNewNote
|
|
############################
|
|
|
|
|
|
@router.post('/create', response_model=Optional[NoteModel])
|
|
async def create_new_note(
|
|
request: Request,
|
|
form_data: NoteForm,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
form_data.access_grants = await filter_allowed_access_grants(
|
|
await Config.get('user.permissions'),
|
|
user.id,
|
|
user.role,
|
|
form_data.access_grants,
|
|
'sharing.public_notes',
|
|
db=db,
|
|
)
|
|
|
|
try:
|
|
note = await Notes.insert_new_note(user.id, form_data, db=db)
|
|
await publish_event(
|
|
request,
|
|
EVENTS.NOTE_CREATED,
|
|
actor=user,
|
|
subject_id=note.id,
|
|
data={'title': note.title},
|
|
)
|
|
return note
|
|
except Exception as e:
|
|
log.exception(e)
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
|
|
############################
|
|
# GetNoteById
|
|
############################
|
|
|
|
|
|
class NoteResponse(NoteModel):
|
|
write_access: bool = False
|
|
|
|
|
|
@router.get('/{id}', response_model=Optional[NoteResponse])
|
|
async def get_note_by_id(
|
|
request: Request,
|
|
id: str,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and (
|
|
not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='read',
|
|
db=db,
|
|
)
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
write_access = (
|
|
user.role == 'admin'
|
|
or (user.id == note.user_id)
|
|
or await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='write',
|
|
db=db,
|
|
)
|
|
or has_public_write_access_grant(note.access_grants)
|
|
)
|
|
|
|
pinned_note_ids = await Notes.get_pinned_note_ids(user.id, db=db)
|
|
return NoteResponse(
|
|
**{**note.model_dump(), 'is_pinned': note.id in pinned_note_ids},
|
|
write_access=write_access,
|
|
)
|
|
|
|
|
|
@router.get('/{id}/chat', response_model=ChatResponse)
|
|
async def get_note_chat_by_id(
|
|
request: Request,
|
|
id: str,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
log.info('[note-chat] get-or-create requested note_id=%s user_id=%s', id, user.id)
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='read',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
chat = await Chats.get_internal_chat_by_note_id(note.id, user.id, db=db)
|
|
if chat:
|
|
log.info('[note-chat] reusing hidden chat note_id=%s chat_id=%s user_id=%s', note.id, chat.id, user.id)
|
|
payload = {**(chat.chat or {})}
|
|
params = {**(payload.get('params') or {})}
|
|
changed = False
|
|
|
|
if params.pop('note_id', None) is not None:
|
|
changed = True
|
|
|
|
system = (
|
|
f'CONTEXT:\nCurrent note id: {note.id}\n'
|
|
'This chat is attached to the current note.\n'
|
|
'For edit requests like make this concise, rewrite, enhance, shorten, or update: call view_note then replace_note_content.\n'
|
|
'Do not say an edit is done unless replace_note_content succeeds.'
|
|
)
|
|
if params.get('system') != system:
|
|
params['system'] = system
|
|
changed = True
|
|
|
|
if payload.pop('system', None) is not None:
|
|
changed = True
|
|
|
|
payload['params'] = params
|
|
if changed:
|
|
updated_chat = await Chats.update_chat_by_id(chat.id, payload, db=db, touch=False)
|
|
if updated_chat:
|
|
return updated_chat
|
|
|
|
return chat
|
|
|
|
chat_id = str(uuid4())
|
|
chat = await Chats.insert_new_chat(
|
|
chat_id,
|
|
user.id,
|
|
ChatForm(
|
|
chat={
|
|
'id': chat_id,
|
|
'title': 'Chat',
|
|
'models': [''],
|
|
'params': {
|
|
'system': (
|
|
f'CONTEXT:\nCurrent note id: {note.id}\n'
|
|
'This chat is attached to the current note.\n'
|
|
'For edit requests like make this concise, rewrite, enhance, shorten, or update: call view_note then replace_note_content.\n'
|
|
'Do not say an edit is done unless replace_note_content succeeds.'
|
|
)
|
|
},
|
|
'history': {'messages': {}, 'currentId': None},
|
|
'messages': [],
|
|
'tags': [],
|
|
}
|
|
),
|
|
db=db,
|
|
internal_meta={'internal': True, 'type': 'note', 'note_id': note.id},
|
|
)
|
|
if not chat:
|
|
log.error('[note-chat] failed creating hidden chat note_id=%s user_id=%s', note.id, user.id)
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
log.info('[note-chat] created hidden chat note_id=%s chat_id=%s user_id=%s', note.id, chat.id, user.id)
|
|
return chat
|
|
|
|
|
|
@router.get('/{id}/chats', response_model=list[ChatResponse])
|
|
async def get_note_chats_by_id(
|
|
request: Request,
|
|
id: str,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='read',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
chats = await Chats.get_internal_chats_by_note_id(note.id, user.id, db=db)
|
|
normalized_chats = []
|
|
for chat in chats:
|
|
payload = {**(chat.chat or {})}
|
|
params = {**(payload.get('params') or {})}
|
|
changed = False
|
|
|
|
if params.pop('note_id', None) is not None:
|
|
changed = True
|
|
|
|
system = (
|
|
f'CONTEXT:\nCurrent note id: {note.id}\n'
|
|
'This chat is attached to the current note.\n'
|
|
'For edit requests like make this concise, rewrite, enhance, shorten, or update: call view_note then replace_note_content.\n'
|
|
'Do not say an edit is done unless replace_note_content succeeds.'
|
|
)
|
|
if params.get('system') != system:
|
|
params['system'] = system
|
|
changed = True
|
|
|
|
if payload.pop('system', None) is not None:
|
|
changed = True
|
|
|
|
payload['params'] = params
|
|
if changed:
|
|
chat = await Chats.update_chat_by_id(chat.id, payload, db=db, touch=False) or chat
|
|
|
|
normalized_chats.append(chat)
|
|
|
|
return normalized_chats
|
|
|
|
|
|
@router.post('/{id}/chat', response_model=ChatResponse)
|
|
async def create_note_chat_by_id(
|
|
request: Request,
|
|
id: str,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='read',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
chat_id = str(uuid4())
|
|
chat = await Chats.insert_new_chat(
|
|
chat_id,
|
|
user.id,
|
|
ChatForm(
|
|
chat={
|
|
'id': chat_id,
|
|
'title': 'Chat',
|
|
'models': [''],
|
|
'params': {
|
|
'system': (
|
|
f'CONTEXT:\nCurrent note id: {note.id}\n'
|
|
'This chat is attached to the current note.\n'
|
|
'For edit requests like make this concise, rewrite, enhance, shorten, or update: call view_note then replace_note_content.\n'
|
|
'Do not say an edit is done unless replace_note_content succeeds.'
|
|
)
|
|
},
|
|
'history': {'messages': {}, 'currentId': None},
|
|
'messages': [],
|
|
'tags': [],
|
|
}
|
|
),
|
|
db=db,
|
|
internal_meta={'internal': True, 'type': 'note', 'note_id': note.id},
|
|
)
|
|
if not chat:
|
|
log.error('[note-chat] failed creating hidden chat note_id=%s user_id=%s', note.id, user.id)
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
log.info('[note-chat] created hidden chat note_id=%s chat_id=%s user_id=%s', note.id, chat.id, user.id)
|
|
return chat
|
|
|
|
|
|
############################
|
|
# UpdateNoteById
|
|
############################
|
|
|
|
|
|
@router.post('/{id}/update', response_model=Optional[NoteModel])
|
|
async def update_note_by_id(
|
|
request: Request,
|
|
id: str,
|
|
form_data: NoteForm,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='write',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
form_data.access_grants = await filter_allowed_access_grants(
|
|
await Config.get('user.permissions'),
|
|
user.id,
|
|
user.role,
|
|
form_data.access_grants,
|
|
'sharing.public_notes',
|
|
db=db,
|
|
)
|
|
|
|
try:
|
|
note = await Notes.update_note_by_id(id, form_data, db=db)
|
|
pinned_note_ids = await Notes.get_pinned_note_ids(user.id, db=db)
|
|
note.is_pinned = note.id in pinned_note_ids
|
|
|
|
event_data = note.model_dump()
|
|
if form_data.data is not None:
|
|
event_data['data'] = {
|
|
key: note.data.get(key) for key in form_data.data.keys() if note.data is not None and key in note.data
|
|
}
|
|
|
|
await sio.emit(
|
|
'events:note',
|
|
event_data,
|
|
to=f'note:{note.id}',
|
|
)
|
|
|
|
await publish_event(
|
|
request,
|
|
EVENTS.NOTE_UPDATED,
|
|
actor=user,
|
|
subject_id=note.id,
|
|
data={'title': note.title},
|
|
)
|
|
return note
|
|
except Exception as e:
|
|
log.exception(e)
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
|
|
############################
|
|
# UpdateNoteAccessById
|
|
############################
|
|
|
|
|
|
class NoteAccessGrantsForm(BaseModel):
|
|
access_grants: list[dict]
|
|
|
|
|
|
@router.post('/{id}/access/update', response_model=Optional[NoteModel])
|
|
async def update_note_access_by_id(
|
|
request: Request,
|
|
id: str,
|
|
form_data: NoteAccessGrantsForm,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='write',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
form_data.access_grants = await filter_allowed_access_grants(
|
|
await Config.get('user.permissions'),
|
|
user.id,
|
|
user.role,
|
|
form_data.access_grants,
|
|
'sharing.public_notes',
|
|
)
|
|
|
|
await AccessGrants.set_access_grants('note', id, form_data.access_grants, db=db)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
pinned_note_ids = await Notes.get_pinned_note_ids(user.id, db=db)
|
|
note.is_pinned = note.id in pinned_note_ids
|
|
await publish_event(
|
|
request,
|
|
EVENTS.NOTE_ACCESS_UPDATED,
|
|
actor=user,
|
|
subject_id=note.id,
|
|
)
|
|
return note
|
|
|
|
|
|
############################
|
|
# PinNoteById
|
|
############################
|
|
|
|
|
|
@router.post('/{id}/pin', response_model=Optional[NoteModel])
|
|
async def pin_note_by_id(
|
|
request: Request,
|
|
id: str,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='read',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
note = await Notes.toggle_note_pinned_by_id(id, user.id, db=db)
|
|
pinned_note_ids = await Notes.get_pinned_note_ids(user.id, db=db)
|
|
note.is_pinned = note.id in pinned_note_ids
|
|
await publish_event(
|
|
request,
|
|
EVENTS.NOTE_PINNED if note.is_pinned else EVENTS.NOTE_UNPINNED,
|
|
actor=user,
|
|
subject_id=note.id,
|
|
subject_type='note',
|
|
)
|
|
return note
|
|
|
|
|
|
############################
|
|
# DeleteNoteById
|
|
############################
|
|
|
|
|
|
@router.delete('/{id}/delete', response_model=bool)
|
|
async def delete_note_by_id(
|
|
request: Request,
|
|
id: str,
|
|
user=Depends(get_verified_user),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
if user.role != 'admin' and not await has_permission(
|
|
user.id, 'features.notes', await Config.get('user.permissions'), db=db
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
)
|
|
|
|
note = await Notes.get_note_by_id(id, db=db)
|
|
if not note:
|
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
|
|
|
if user.role != 'admin' and (
|
|
user.id != note.user_id
|
|
and not await AccessGrants.has_access(
|
|
user_id=user.id,
|
|
resource_type='note',
|
|
resource_id=note.id,
|
|
permission='write',
|
|
db=db,
|
|
)
|
|
):
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
|
|
|
try:
|
|
note = await Notes.delete_note_by_id(id, db=db)
|
|
await publish_event(
|
|
request,
|
|
EVENTS.NOTE_DELETED,
|
|
actor=user,
|
|
subject_id=id,
|
|
)
|
|
return True
|
|
except Exception as e:
|
|
log.exception(e)
|
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT())
|